correct config namespace

correct config namespace

--- lib/config.coffee
+++ lib/config.coffee
@@ -95,7 +95,7 @@
         body.setAttribute('isotope-ui-tooltip-lowcontrast', 'false')

     applyEditorFont = () ->
-      if atom.config.get('isotope-ui.matchEditorFont')
+      if atom.config.get('isotope-light-ui.matchEditorFont')
         body.style.fontFamily = atom.config.get('editor.fontFamily')
       else
         body.style.fontFamily = ''
@@ -151,7 +151,7 @@
     atom.config.onDidChange 'isotope-light-ui.lowContrastTooltip', ->
       applyTooltipContrast()

-    atom.config.onDidChange 'isotope-ui.matchEditorFont', ->
+    atom.config.onDidChange 'isotope-light-ui.matchEditorFont', ->
       applyEditorFont()

     atom.config.onDidChange 'editor.fontFamily', ->


Fix specs

Fix specs

--- lib/buffer-search.coffee
+++ lib/buffer-search.coffee
@@ -16,7 +16,8 @@

     recreateMarkers = @recreateMarkers.bind(this)
     @findOptions.onDidChange (changedParams) =>
-      return unless changedParams? or changedParams.findPattern? or
+      return unless changedParams?
+      return unless changedParams.findPattern? or
         changedParams.useRegex? or
         changedParams.wholeWord? or
         changedParams.caseSensitive? or


Fixed applying of mode property

Fixed applying of mode property

--- lib/flex-tool-bar.coffee
+++ lib/flex-tool-bar.coffee
@@ -182,7 +182,7 @@

         button = @buttonTypes[btn.type](@toolBar, btn) if @buttonTypes[btn.type]

-        button.addClass "tool-bar-mode-#{btn.mode}" if btn.mode
+        @addClass button.element, "tool-bar-mode-#{btn.mode}" if btn.mode

         if btn.style?
           for propName, v of btn.style
@@ -190,6 +190,12 @@

         if ( btn.disable? && @grammarCondition(btn.disable) ) or ( btn.enable? && !@grammarCondition(btn.enable) )
           button.setEnabled false
+
+  addClass: (el, className) ->
+    if el.classList
+      el.classList.add(className)
+    else
+      el.className += " #{className}"

   loadConfig: ->
     ext = path.extname @configFilePath


Make all ticks in GMT.

Make all ticks in GMT.

--- bokehjs/src/coffee/common/ticking.coffee
+++ bokehjs/src/coffee/common/ticking.coffee
@@ -425,29 +425,29 @@
   last_day_no_later_than = (time) ->
     # FIXME Is this really the best way?
     d = new Date(time)
-    d.setHours(0)
-    d.setMinutes(0)
-    d.setSeconds(0)
-    d.setMilliseconds(0)
+    d.setUTCHours(0)
+    d.setUTCMinutes(0)
+    d.setUTCSeconds(0)
+    d.setUTCMilliseconds(0)
     return d.getTime()

   last_month_no_later_than = (time) ->
     d = new Date(time)
-    d.setDate(1)
-    d.setHours(0)
-    d.setMinutes(0)
-    d.setSeconds(0)
-    d.setMilliseconds(0)
+    d.setUTCDate(1)
+    d.setUTCHours(0)
+    d.setUTCMinutes(0)
+    d.setUTCSeconds(0)
+    d.setUTCMilliseconds(0)
     return d.getTime()

   last_year_no_later_than = (time) ->
     d = new Date(time)
-    d.setMonth(0)
-    d.setDate(1)
-    d.setHours(0)
-    d.setMinutes(0)
-    d.setSeconds(0)
-    d.setMilliseconds(0)
+    d.setUTCMonth(0)
+    d.setUTCDate(1)
+    d.setUTCHours(0)
+    d.setUTCMinutes(0)
+    d.setUTCSeconds(0)
+    d.setUTCMilliseconds(0)
     return d.getTime()

   copy_date = (date) ->
@@ -493,14 +493,14 @@
         start_date = new Date(last_year_no_later_than(start_time))

         end_date = new Date(last_year_no_later_than(end_time))
-        end_date.setFullYear(end_date.getFullYear() + 1)
+        end_date.setUTCFullYear(end_date.getUTCFullYear() + 1)

         dates = []
         date = start_date
         while true
           dates.push(copy_date(date))

-          date.setFullYear(date.getFullYear() + 1)
+          date.setUTCFullYear(date.getUTCFullYear() + 1)
           if date > end_date
             break

@@ -512,7 +512,7 @@
       months_of_year = (year_date) ->
         return months.map((month) ->
           month_date = copy_date(year_date)
-          month_date.setMonth(month)
+          month_date.setUTCMonth(month)
           return month_date)

       # FIXME Use a list comprehension?
@@ -542,14 +542,15 @@
         # XXX This is not a reliable technique in general, but it should be
         # safe when the day of the month is 1.  (The problem case is this:
         # Mar 31 -> Apr 31, which becomes May 1.)
-        end_date.setMonth(end_date.getMonth() + 1)
+        prev_end_date = copy_date(end_date)
+        end_date.setUTCMonth(end_date.getUTCMonth() + 1)

         dates = []
         date = start_date
         while true
           dates.push(copy_date(date))

-          date.setMonth(date.getMonth() + 1)
+          date.setUTCMonth(date.getUTCMonth() + 1)
           if date > end_date
             break

@@ -564,14 +565,15 @@
         dates = []
         for day in days
           day_date = copy_date(month_date)
-          day_date.setDate(day)
+          day_date.setUTCDate(day)
           # We can't use all of the values in @days, because they may not fall
           # within the current month.  In fact, if, e.g., our month is 28 days
           # and we're marking every third day, we don't want day 28 to show up
           # because it'll be right next to the 1st of the next month.  So we
           # make sure we have a bit of room before we include a day.
           future_date = new Date(day_date.getTime() + (typical_interval / 2))
-          if future_date.getMonth() == month_date.getMonth()
+          if future_date.getUTCMonth() == month_date.getUTCMonth()
+            console.log("  push #{month_date} / #{month_date.toUTCString()} ~ (#{month_date.getUTCDate()} -> #{day}) = #{day_date}")
             dates.push(day_date)
         return dates

@@ -582,11 +584,6 @@
       # FIXME Since the ticks are sorted, this could be done more efficiently.
       ticks_in_range = _.filter(all_ticks,
                                 ((tick) -> data_low <= tick <= data_high))
-
-      # FIXME
-      # Transition between 12-hour and 24-hour resolutions is not correct.
-      # This is because the day resolutions are aligned with the local time
-      # zone, and the resolutions below are aligned with GMT.

       return ticks_in_range

@@ -634,10 +631,7 @@
     new MonthsScale(arange(0, 12, 4)),
     new MonthsScale(arange(0, 12, 6)),

-    # FIXME Add a year scale.
-
     # Catchall for large timescales.
-    # FIXME I don't think this scale is working.
     new AdaptiveScale([1.0, 2.0, 5.0], 10.0, ONE_YEAR, Infinity),
   ])

@@ -647,7 +641,7 @@
     ticks = global_scale.get_ticks(data_low, data_high)

 #     if ticks.length > 0 and ticks[0] > 10000000000
-#       dates = [new Date(tick) for tick in ticks]
+#       dates = (new Date(tick) for tick in ticks)
 #       console.log("returning #{dates}")

     return ticks
@@ -838,6 +832,13 @@
         @formats[fmt_name] = [sizes, fmt_strings]
       return

+    # FIXME I think these names are misleading.  I suggest:
+    # interval -> span ("interval" means something else in other parts of this
+    #                   file)
+    # resol -> res_str (using "resolution" and "resol" to refer to different
+    #                   concepts?  Have we lived and fought in vain?)
+    # While we're at it, we could probably change the name of the method as
+    # well (e.g., "get_resolution_str").
     _get_resolution: (resolution, interval) ->
       r = resolution
       span = interval
@@ -883,14 +884,22 @@

       [widths, formats] = @formats[resol]
       format = formats[0]
+      # FIXME I'm pretty sure this code won't work; luckily it doesn't seem to
+      # be used.
       if char_width
         # If a width is provided, then we pick the most appropriate scale,
         # otherwise just use the widest format
         good_formats = []
         for i in [0...widths.length]
           if widths[i] * ticks.length < fill_ratio * char_width
+            # FIXME I think "@formats" should be "formats".  (Perhaps they
+            # should have more distinct names.)
             good_formats.push(@formats[i])
         if good_formats.length > 0
+          # FIXME I think this should be:
+          #   format = good_formats[good_formats.length - 1]
+          # or better yet:
+          #   format = _.last(good_formats)
           format = good_formats[ticks.length-1]

       # Apply the format to the tick values
@@ -918,6 +927,9 @@
       # with hybrid formats in a reasonable manner.
       for t in ticks
         try
+          # FIXME This should be:
+          #   dt = new Date(t)
+          # Or rather, if anyone used dt, that's what it should be.
           dt = Date(t)
           tm = _array(t)
           s = _strftime(t, format)
@@ -955,6 +967,7 @@
             # A label such as '000ms' should leave one zero.
             ss = '0' + ss
           labels.push(ss)
+#           console.log("  #{t} -> #{new Date(t)} : #{tm} : #{s} -> #{ss}")
         else
           labels.push(s)


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/active_bounds.coffee
+++ test/active_bounds.coffee
@@ -19,7 +19,7 @@

   obj.update()

-
+  ok destroySpy, 'obj should be destroyed when it goes outside the x bounds'

   obj2 = GameObject
     x: 100


Updated assets to AMD

Updated assets to AMD

--- src/milksugar/assets.coffee
+++ src/milksugar/assets.coffee
@@ -1,11 +1,13 @@
-do (window = @, MilkSugar = @MilkSugar or= {}) ->
+define 'milksugar/assets', ->
   "use strict";

-  MilkSugar.Assets =
+  Assets =
     root: 'assets'
     add: (pathName, alias = "#{pathName}s") ->
       @[pathName] = (assetName) ->
         realPathName = if alias then alias else pathName
         MilkSugar.Assets.path MilkSugar.Assets.root, realPathName, assetName
     remove: (pathName) -> delete @[pathName] if @[pathName]
-    path: (paths...) -> paths.join '/'+    path: (paths...) -> paths.join '/'
+
+  return Assets


:art:

:art:

--- lib/scroll.coffee
+++ lib/scroll.coffee
@@ -67,12 +67,8 @@
   moveToFirstCharacterOfLine: ->
     @editor.moveToFirstCharacterOfLine()

-  getOffSetPixelHeight: (where) ->
-    lineCount =
-      switch where
-        when 'top' then @scrolloff
-        when 'bottom' then @scrolloff + 1
-    @editor.getLineHeightInPixels() * lineCount
+  getOffSetPixelHeight: (lineDelta=0) ->
+    @editor.getLineHeightInPixels() * (@scrolloff + lineDelta)

 class ScrollCursorToTop extends ScrollCursor
   @extend()
@@ -80,7 +76,7 @@
     not (@rows.last is @rows.final)

   getScrollTop: ->
-    @pixelCursorTop - @getOffSetPixelHeight('top')
+    @pixelCursorTop - @getOffSetPixelHeight()

 class ScrollCursorToBottom extends ScrollCursor
   @extend()
@@ -88,7 +84,7 @@
     not (@rows.first is 0)

   getScrollTop: ->
-    @pixelCursorTop - (@editor.getHeight() - @getOffSetPixelHeight('bottom'))
+    @pixelCursorTop - (@editor.getHeight() - @getOffSetPixelHeight(1))

 class ScrollCursorToMiddle extends ScrollCursor
   @extend()
@@ -122,5 +118,14 @@
     @editor.setScrollRight(@pixelCursorTop)
     @putCursorOnScreen()

-module.exports = {ScrollDown, ScrollUp, ScrollCursorToTop, ScrollCursorToMiddle,
-  ScrollCursorToBottom, ScrollCursorToLeft, ScrollCursorToRight}
+module.exports = {
+  ScrollDown,
+  ScrollUp,
+
+  ScrollCursorToTop,
+  ScrollCursorToMiddle,
+  ScrollCursorToBottom,
+
+  ScrollCursorToLeft,
+  ScrollCursorToRight
+ }


consistent whitespace

consistent whitespace

--- lib/scroll.coffee
+++ lib/scroll.coffee
@@ -23,7 +23,6 @@
     lastScreenRow = @editorView.getLastVisibleScreenRow() - @scrolloff
     @editorView.scrollToBufferPosition([lastScreenRow + count, 0])

-
 class ScrollUp extends Scroll
   constructor: (@editorView, @editor, @scrolloff) ->
     super(@editorView, @editor)
@@ -42,5 +41,4 @@
     firstScreenRow = @editorView.getFirstVisibleScreenRow() + @scrolloff
     @editorView.scrollToBufferPosition([firstScreenRow - count, 0])

-
 module.exports = { ScrollDown, ScrollUp }


Updated server

Updated server

--- lib/server.coffee
+++ lib/server.coffee
@@ -6,6 +6,7 @@
 Q = require 'q'
 SocketIO = require 'socket.io'
 log = require './helpers/log'
+Scheduler = require './models/scheduler'

 class Server

@@ -17,15 +18,13 @@
     @pool = {}
     @io = SocketIO.listen(server.listener)
     @io.sockets.on 'connection', (socket)=>
+
       @pool[socket.id] = socket
       log.warn 'Client connected', socket.id

       socket.on 'disconnect', =>
         delete @pool[socket.id]
         log.warn 'client disconnected', socket.id
-
-    socket.emit({ msg: 'welcome' });
-});

   _send: (t)->
     key = Object.keys(@pool)[0]


addHotkey can now register multiple keys at once

--- src/modules/keyboard.coffee
+++ src/modules/keyboard.coffee
@@ -44,11 +44,9 @@
     toolbar.setActive(format, value) if toolbar?

   _initDeletes: ->
-    _.each([dom.KEYS.DELETE, dom.KEYS.BACKSPACE], (key) =>
-      this.addHotkey(key, =>
-        # Prevent deleting if editor is already blank (or just empty newline)
-        return @quill.getLength() > 1
-      )
+    this.addHotkey([dom.KEYS.DELETE, dom.KEYS.BACKSPACE], =>
+      # Prevent deleting if editor is already blank (or just empty newline)
+      return @quill.getLength() > 1
     )

   _initHotkeys: ->


nml-parser babel now is optional

nml-parser babel now is optional

--- src/nml-parser/index.coffee
+++ src/nml-parser/index.coffee
@@ -1,9 +1,11 @@
 'use strict'

-babel = require 'babel-core'
+try
+    babel = require 'babel-core'
 bundle = require './bundle'
 parser = require './parser'
 bindingParser = require 'src/binding/parser'
+log = require 'src/log'
 utils = require 'src/utils'
 Renderer = require 'src/renderer'
 assert = require 'src/assert'
@@ -103,9 +105,14 @@
         args += elem.params+''

         # body
-        body = "(function(){#{elem.body}})"
-        body = babel.transform(body, presets: ['es2015']).code
-        body = /{([^]*)}/m.exec(body)?[1]
+        {body} = elem
+        if babel
+            body = "(function(){#{body}})"
+            body = babel.transform(body, presets: ['es2015']).code
+            body = /{([^]*)}/m.exec(body)?[1]
+        else
+            log.warn.once 'NML functions do not support EcmaScript 6, ' +
+                'because babel is not installed'

         "function(#{args}){#{body}}"
     object: (elem) ->


Atom removed Whitespaces

Atom removed Whitespaces

--- lib/viewer.coffee
+++ lib/viewer.coffee
@@ -41,8 +41,8 @@

     if @tabView? and @tabView.title isnt newTitle and\
         atom.workspace.paneForItem(@tabView)?
-      atom.workspace.paneForItem(@tabView).getItems().find((p) =>
-        p is @tabView)?.updateTitle(newTitle)
+      atom.workspace.paneForItem(@tabView).getItems().find((p) =>
+        p is @tabView)?.updateTitle(newTitle)
     else if @window? and !@window.isDestroyed() and @window.getTitle() isnt newTitle
       @window.setTitle("""Atom-LaTeX PDF Viewer - [#{@latex.mainFile}]""")
     @client.ws?.send JSON.stringify type: "refresh"
@@ -54,7 +54,7 @@
   focusViewer: ->
     if @window? and !@window.isDestroyed()
       @window.setBounds(@window.getBounds())
-      @window.focus()
+      @window.focus()

   focusMain: ->
     @self.focus() if @self? and !@self.focused


Require solo polyfills in testing platform.

--- src/platform/testing.coffee
+++ src/platform/testing.coffee
@@ -1,3 +1,5 @@
+#= require ./solo
+
 Batman.Request.setupMockedResponse = ->
   Batman.Request.mockedResponses = {}


splashscreen.autohide has a default value of true even if it's later overridden to take care of projects that might not have the flag

--- src/steroids/project/supersonic-config.coffee
+++ src/steroids/project/supersonic-config.coffee
@@ -13,6 +13,8 @@
       postMake:
         cmd: null
         args: null
+    splashscreen:
+      autohide: true

   constructor: ->
     @appConfigPath = paths.application.configs.app


repaired ga int issue

repaired ga int issue

--- app/assets/javascripts/analytics/presenter.coffee
+++ app/assets/javascripts/analytics/presenter.coffee
@@ -92,7 +92,7 @@

     if $('dfn.JS-tooltip.hovered').length > 0
       keyword = $('dfn.JS-tooltip.hovered').html()
-      time = $('dfn.JS-tooltip.hovered').attr('timeHovered')
+      time = parseInt($('dfn.JS-tooltip.hovered').attr('timeHovered'))
       ga?(
         'send', 'event', 'TooltipRead', 'hoverout', 'tooltipActive:true;' +
         "keyword:#{keyword}", time


remove obsolete jit-based graph code.

remove obsolete jit-based graph code.

--- app/assets/javascripts/plugins/jitGraph.js.coffee
+++ app/assets/javascripts/plugins/jitGraph.js.coffee
@@ -1,72 +0,0 @@
-return unless $("#infovis")[0]
-
-ht = new $jit.Hypertree(
-
-  #id of the visualization container
-  injectInto: "infovis"
-
-  #canvas width and height
-  width: 500
-  height: 350
-
-  #Change node and edge styles such as
-  #color, width and dimensions.
-  Node:
-    dim: 9
-    color: "#f00"
-
-  Edge:
-    lineWidth: 2
-    color: "#088"
-
-  #Attach event handlers and add text to the
-  #labels. This method is only triggered on label
-  #creation
-  onCreateLabel: (domElement, node) ->
-    domElement.innerHTML = node.name
-    $jit.util.addEvent domElement, "click", ->
-      ht.onClick node.id,
-        onComplete: ->
-          ht.controller.onComplete()
-
-  #Change node styles when labels are placed
-  #or moved.
-  onPlaceLabel: (domElement, node) ->
-    style = domElement.style
-    style.display = ""
-    style.cursor = "pointer"
-    if node._depth <= 1
-      style.fontSize = "0.8em"
-      style.color = "#555"
-    else if node._depth is 2
-      style.fontSize = "0.7em"
-      style.color = "#999"
-    else
-      style.display = "none"
-    left = parseInt(style.left)
-    w = domElement.offsetWidth
-    style.left = (left - w / 2) + "px"
-
-  onComplete: ->
-
-    #Build the right column relations list.
-    #This is done by collecting the information (stored in the data property)
-    #for all the nodes adjacent to the centered node.
-    node = ht.graph.getClosestNodeToOrigin("current")
-    html = "
" + node.name + "
Connections:"
-    html += "
"
-    node.eachAdjacency (adj) ->
-      child = adj.nodeTo
-      if child.data
-        url = child.data.url
-        html += "
" + child.name + " " + "
(relation: " + url + ")

"
-
-    html += "
"
-    $jit.id("infoviscontext").innerHTML = html
-)
-$ ->
-  ht.loadJSON graph
-
-  #compute positions and plot.
-  ht.refresh()
-  ht.controller.onComplete()


Fix autocompletion for competitors with two first or last names

--- app/assets/javascripts/wca-autocomplete.js.coffee
+++ app/assets/javascripts/wca-autocomplete.js.coffee
@@ -31,7 +31,7 @@
   auto.bind "typeahead:selected", (event, object) ->
     names = object.name.split(" ")
     firstName = names[0]
-    lastName = names[1]
+    lastName = names.slice(1, names.length).join(" ")
     country = object.countryId
     gender = object.gender


Removing now unnecessary braces from the grammar. Still compiles to identical JS.

--- grammar.coffee
+++ grammar.coffee
@@ -48,7 +48,7 @@
 # `$1` would be the value of the first `Expression`, `$2` would be the token
 # for the `UNLESS` terminal, and `$3` would be the value of the second
 # `Expression`.
-grammar = {
+grammar =

   # The **Root** is the top-level node in the syntax tree. Since we parse bottom-up,
   # all parsing must end here.
@@ -402,9 +402,9 @@
   # The condition portion of a while loop.
   WhileSource: [
     o "WHILE Expression",                       -> new WhileNode $2
-    o "WHILE Expression WHEN Expression",       -> new WhileNode $2, {guard : $4}
-    o "UNTIL Expression",                       -> new WhileNode $2, {invert: true}
-    o "UNTIL Expression WHEN Expression",       -> new WhileNode $2, {invert: true, guard: $4}
+    o "WHILE Expression WHEN Expression",       -> new WhileNode $2, guard: $4
+    o "UNTIL Expression",                       -> new WhileNode $2, invert: true
+    o "UNTIL Expression WHEN Expression",       -> new WhileNode $2, invert: true, guard: $4
   ]

   # The while loop can either be normal, with a block of expressions to execute,
@@ -431,7 +431,7 @@
   ]

   ForBody: [
-    o "FOR Range",                              -> {source: new ValueNode($2), vars: []}
+    o "FOR Range",                              -> source: new ValueNode($2), vars: []
     o "ForStart ForSource",                     -> $2.raw = $1.raw; $2.vars = $1; $2
   ]

@@ -460,13 +460,13 @@
   # clause. If it's an array comprehension, you can also choose to step through
   # in fixed-size increments.
   ForSource: [
-    o "IN Expression",                               -> {source: $2}
-    o "OF Expression",                               -> {source: $2, object: true}
-    o "IN Expression WHEN Expression",               -> {source: $2, guard: $4}
-    o "OF Expression WHEN Expression",               -> {source: $2, guard: $4, object: true}
-    o "IN Expression BY Expression",                 -> {source: $2, step:  $4}
-    o "IN Expression WHEN Expression BY Expression", -> {source: $2, guard: $4, step:   $6}
-    o "IN Expression BY Expression WHEN Expression", -> {source: $2, step:  $4, guard: $6}
+    o "IN Expression",                               -> source: $2
+    o "OF Expression",                               -> source: $2, object: true
+    o "IN Expression WHEN Expression",               -> source: $2, guard: $4
+    o "OF Expression WHEN Expression",               -> source: $2, guard: $4, object: true
+    o "IN Expression BY Expression",                 -> source: $2, step:  $4
+    o "IN Expression WHEN Expression BY Expression", -> source: $2, guard: $4, step:   $6
+    o "IN Expression BY Expression WHEN Expression", -> source: $2, step:  $4, guard: $6
   ]

   # The CoffeeScript switch/when/else block replaces the JavaScript
@@ -487,8 +487,8 @@

   # An individual **When** clause, with action.
   When: [
-    o "LEADING_WHEN SimpleArgs Block",            -> new IfNode $2, $3, {statement: true}
-    o "LEADING_WHEN SimpleArgs Block TERMINATOR", -> new IfNode $2, $3, {statement: true}
+    o "LEADING_WHEN SimpleArgs Block",            -> new IfNode $2, $3, statement: true
+    o "LEADING_WHEN SimpleArgs Block TERMINATOR", -> new IfNode $2, $3, statement: true
   ]

   # The most basic form of *if* is a condition and an action. The following
@@ -496,7 +496,7 @@
   # ambiguity.
   IfBlock: [
     o "IF Expression Block",                    -> new IfNode $2, $3
-    o "UNLESS Expression Block",                -> new IfNode $2, $3, {invert: true}
+    o "UNLESS Expression Block",                -> new IfNode $2, $3, invert: true
     o "IfBlock ELSE IF Expression Block",       -> $1.addElse (new IfNode($4, $5)).forceStatement()
     o "IfBlock ELSE Block",                     -> $1.addElse $3
   ]
@@ -505,10 +505,10 @@
   # *if* and *unless*.
   If: [
     o "IfBlock"
-    o "Statement IF Expression",                -> new IfNode $3, Expressions.wrap([$1]), {statement: true}
-    o "Expression IF Expression",               -> new IfNode $3, Expressions.wrap([$1]), {statement: true}
-    o "Statement UNLESS Expression",            -> new IfNode $3, Expressions.wrap([$1]), {statement: true, invert: true}
-    o "Expression UNLESS Expression",           -> new IfNode $3, Expressions.wrap([$1]), {statement: true, invert: true}
+    o "Statement IF Expression",                -> new IfNode $3, Expressions.wrap([$1]), statement: true
+    o "Expression IF Expression",               -> new IfNode $3, Expressions.wrap([$1]), statement: true
+    o "Statement UNLESS Expression",            -> new IfNode $3, Expressions.wrap([$1]), statement: true, invert: true
+    o "Expression UNLESS Expression",           -> new IfNode $3, Expressions.wrap([$1]), statement: true, invert: true
   ]

   # Arithmetic and logical operators, working on one or more operands.
@@ -572,7 +572,6 @@
     o "Expression ! OF Expression",             -> new OpNode '!', new ParentheticalNode new OpNode 'in', $1, $4
   ]

-}

 # Precedence
 # ----------


Add a couple tests, incl a failing array test

--- regression/src/matchers-test.coffee
+++ regression/src/matchers-test.coffee
@@ -87,6 +87,9 @@
       Then -> @matches(td.matchers.contains(true, 5, null, undefined), [true, 5, undefined, null]) == true
       Then -> @matches(td.matchers.contains(true, 5, null, undefined), [true, 5, null]) == false

+      describe 'elements containing matchers', ->
+        Then -> @matches(td.matchers.contains('b', td.matchers.isA(Number)), ['a', 3, 'b']) == true
+
     context 'objects', ->
       Then -> @matches(td.matchers.contains(foo: 'bar', baz: 42), foo: 'bar', baz: 42, stuff: this) == true
       Then -> @matches(td.matchers.contains(foo: 'bar', lol: 42), foo: 'bar', baz: 42) == false
@@ -100,6 +103,12 @@
         ) == true

       describe 'objects containing matchers', ->
+        Then -> @matches(td.matchers.contains({a: td.matchers.contains(1,2)}),
+            {a: [4,1,2,3]}
+          ) == true
+        Then -> @matches(td.matchers.contains({a: td.matchers.contains(1,5)}),
+            {a: [4,1,2,3]}
+          ) == false
         Then -> @matches(
             td.matchers.contains({someString: td.matchers.isA(String)}),
             {someString: "beautifulString"}
@@ -126,6 +135,14 @@
             }),
             {nested: {nestedString: "abc", irrelevant: true}, irrelevantHere: "alsoTrue"}
           ) == true
+        Then -> @matches(
+            td.matchers.contains({
+              nested: td.matchers.contains({
+                nestedString: td.matchers.isA(Number)
+              })
+            }),
+            {nested: {nestedString: "abc", irrelevant: true}, irrelevantHere: "not a number!"}
+          ) == false

     context 'regexp', ->


manage localstrage full error

manage localstrage full error

--- app/front/js/services/file_manager_service.coffee
+++ app/front/js/services/file_manager_service.coffee
@@ -2,6 +2,14 @@

   PREFIX = "LOCAL"
   FM = {}
+
+  _store = (key, value) ->
+    try
+      return localStorage.setItem(key, value)
+    catch e
+      if e.code == 22
+        localStorage.clear()
+        return localStorage.setItem(key, value)

   _read_from_file_system = (file) ->
     deferred = $q.defer()
@@ -9,18 +17,18 @@
     reader.readAsText(file)
     reader.onload = do (file) ->
       (e) ->
-        localStorage.setItem("#{PREFIX}/#{file.name}", e.target.result.trim())
+        _store("#{PREFIX}/#{file.name}", e.target.result.trim())
         deferred.resolve(file.name)
     return deferred.promise

   Object.defineProperty FM, "external_themes", {
     get: -> angular.fromJson(localStorage.getItem("external_themes") || [])
-    set: (new_val) -> localStorage.setItem('external_themes', angular.toJson(new_val))
+    set: (new_val) -> _store('external_themes', angular.toJson(new_val))
   }

   Object.defineProperty FM, "local_themes", {
     get: -> angular.fromJson(localStorage.getItem("local_themes") || [])
-    set: (new_val) -> localStorage.setItem('local_themes', angular.toJson(new_val))
+    set: (new_val) -> _store('local_themes', angular.toJson(new_val))
   }

   FM.add_external_theme = (theme_obj) ->
@@ -47,7 +55,7 @@
     localStorage.getItem("#{prefix}/#{file_name}")

   FM.save = (file_name, content, prefix = PREFIX) ->
-    localStorage.setItem("#{prefix}/#{file_name}", content)
+    _store("#{prefix}/#{file_name}", content)

   FM.remove = (file, prefix = PREFIX) ->
     @local_themes = @local_themes.remove((item) -> item.name == file.name)


Make leveling slower

Make leveling slower

--- app/scripts/components/weapons/WeaponLaser.coffee
+++ app/scripts/components/weapons/WeaponLaser.coffee
@@ -26,7 +26,7 @@

   determineLevel: (xp) ->
     levelBoundaries = [150, 400, 1000, 2500]
-    levelBoundaries = [15, 40, 100, 250]
+    #levelBoundaries = [15, 40, 100, 250]
     neededXP = 0
     level = 0
     for i in levelBoundaries


Fix normalize.css requirement

Fix normalize.css requirement

--- client/app/application.coffee
+++ client/app/application.coffee
@@ -5,7 +5,7 @@
 router and app_layout view.
 ###

-require 'normalize.css'
+require 'normalize.css/normalize.css'
 require './styles/app.styl'

 {Application} = require 'backbone.marionette'


Update en.coffee

Update en.coffee

--- t9n/en.coffee
+++ t9n/en.coffee
@@ -127,7 +127,7 @@

       #---- Misc...
       "Unknown error": "Unknown error"
-      "Error, too many requests. Please slow down. You must wait 1 seconds before trying again."
+      "Error, too many requests. Please slow down. You must wait 1 seconds before trying again.": "Error, too many requests. Please slow down. You must wait 1 seconds before trying again."

 T9n.map "en", en


remove testing for exact filename for now since remote dl url does not have plain file name to check against

--- test-integration/helpers/ui/scores.coffee
+++ test-integration/helpers/ui/scores.coffee
@@ -1,5 +1,6 @@
 fs = require 'fs'
 path = require 'path'
+_ = require 'underscore'
 selenium = require 'selenium-webdriver'
 io = require 'selenium-webdriver/io'

@@ -67,14 +68,20 @@
   isExportDownloaded: =>
     @el.exportSucceeded().findElement().getAttribute('src').then (src) =>
       file = path.basename(src)
-      path = "#{@test.downloadDirectory}/#{file}"
-
-      io.exists(path)
+      # TODO get actual filename to test for full file path
+      io.exists(@test.downloadDirectory)

     .then (isExportDownloaded) =>
+      @cleanUpDownloadedExports() if isExportDownloaded
+      isExportDownloaded

-      io.unlink(path)
-      isExportDownloaded
+  cleanUpDownloadedExports: =>
+    fs.readdir @test.downloadDirectory, (err, files) =>
+      _.each(files, (file) =>
+        fullPath = "#{@test.downloadDirectory}/#{file}"
+        @test.utils.verbose("Removing #{fullPath}.")
+        io.unlink(fullPath)
+      )

   hoverCCTooltip: =>
     @el.hoverCCTooltip().findElement().then (e) =>


Show warning from hacking module.

Show warning from hacking module.


- fix failing test

- fix failing test

--- test/src/specs/ExtensionStats.spec.coffee
+++ test/src/specs/ExtensionStats.spec.coffee
@@ -29,13 +29,13 @@
       es.__set__("TwitterPublisher", TW)

     it "should return a timeline with 50 tweets for the last 7 days (default)", (done) ->
-      p = es.getTweets(new Date(2014, 4, 4, 18, 0))
+      p = es.getTweets(new Date(Date.UTC(2014, 4, 4, 21, 0)))
       p.done (tweets) ->
         expect(tweets.length).to.equal(50)
         done()

     it "should not return any tweets", (done) ->
-      p = es.getTweets(new Date(2014, 7, 4, 18, 0))
+      p = es.getTweets(new Date(Date.UTC(2014, 7, 4, 18, 0)))
       p.done (tweets) ->
         expect(tweets.length).to.equal(0)
         done()
@@ -44,7 +44,7 @@
       # the last tweet in the test data is of 2014/5/4
       # month is 0 based => 5-1 = 4
       # 5/4/2014 6:00 pm
-      p = es.getTweets(new Date(2014, 4, 4, 18, 0), 5)
+      p = es.getTweets(new Date(Date.UTC(2014, 4, 4, 23, 0)), 5)
       p.done (tweets) ->
         expect(tweets.length).to.equal(34)
         done()


Option to parse incoming regardless it fails or not

--- components/ParseJson.coffee
+++ components/ParseJson.coffee
@@ -3,19 +3,27 @@

 class ParseJson extends noflo.Component
   constructor: ->
+    @try = true
+
     @inPorts =
       in: new noflo.Port()
+      try: new noflo.Port()
     @outPorts =
       out: new noflo.Port()
+
+    @inPorts.in.on "data", (data) =>
+      @try = false if data is "false"

     @inPorts.in.on "begingroup", (group) =>
       @outPorts.out.beginGroup group

     @inPorts.in.on "data", (data) =>
       try
-        @outPorts.out.send JSON.parse data
+        data = JSON.parse data
       catch e
-        @outPorts.out.send data
+        data = JSON.parse data unless @try
+
+      @outPorts.out.send data

     @inPorts.in.on "endgroup", =>
       @outPorts.out.endGroup()


Add throw type to log.array()

Add throw type to log.array()

--- lib/resin/log/log.coffee
+++ lib/resin/log/log.coffee
@@ -75,7 +75,7 @@
 # @param {Array} array array
 # @param {Function} logFunction log function (e.g: resin.log.info)
 #
-# @throw Will throw if logFunction is not a function
+# @throw {Error} Will throw if logFunction is not a function
 #
 exports.array = (array, logFunction) ->
 	return if not array?


Refactor tests, more useful

Refactor tests, more useful

--- test/test.coffee
+++ test/test.coffee
@@ -4,56 +4,106 @@
 randomInterval = (min, max) ->
   Math.floor(Math.random()*(max-min+1)+min)

-printLogs = (instance) ->
-  instance.error 'error message'
-  instance.warn 'warn message'
-  instance.success 'success message'
-  instance.info 'info message'
-  instance.verbose 'verbose message'
-  instance.debug 'debug message'
-  instance.silly 'silly message'
+printLogs = (acho) ->
+  acho.error 'error message'
+  acho.warn 'warn message'
+  acho.success 'success message'
+  acho.info 'info message'
+  acho.verbose 'verbose message'
+  acho.debug 'debug message'
+  acho.silly 'silly message'
+
+createFakeTransport = ->
+  store = []
+  transport = ->
+    console.log.apply(console, arguments)
+    store.push.apply(store, arguments)
+  transport.store = store
+  transport

 describe 'Acho ::', ->

-  before  ->
+  beforeEach ->
     opts =
       color: true
       outputType: (type) -> "[#{type}] » "
+      transport: createFakeTransport()
     @acho = Acho opts

-  it 'create a new object', ->
-    @acho.should.be.object
+  describe 'initialization', ->

-  it 'add a message into the collection', ->
-    @acho.push 'error', 'hello world'
-    @acho.messages.error.length.should.be.equal 1
+    it 'invoke constructor without new keyword', ->
+      @acho.should.be.an.object

-  it 'add a message and print', ->
-    @acho.add 'error', 'hello world'
-    @acho.messages.error.length.should.be.equal 2
+    it 'invoke constructor new keyword', ->
+      new Acho().should.be.an.object

-  it 'print a normal message', ->
-    @acho.warn 'warn message'
+  describe 'internal store', ->

-  it 'change the color behavior',  ->
-    @acho.types.error.color = 'red bold'
-    @acho.print()
+    it 'passing a initial store state', ->
+      instance = Acho
+        transport: createFakeTransport()
+        messages:
+          info: ['info message']

-  it 'print the messages', ->
-    @acho.print()
+      instance.print()
+      instance.transport.store.length.should.be.equal 1
+      expected = '\u001b[37minfo\t\u001b[39m\u001b[90minfo message\u001b[39m'
+      instance.transport.store[0].should.be.equal expected

-  it 'try to print a message out of the level',  ->
-    @acho.verbose 'test of message'
+    it '.push: add message into a internal level collection', ->
+      @acho.push 'error', 'hello world'
+      @acho.messages.error.length.should.be.equal 1

-  describe 'logs', ->
+    it '.add: push and print a message', ->
+      @acho.add 'error', 'hello world'
+
+      @acho.transport.store.length.should.be.equal 1
+      @acho.messages.error.length.should.be.equal 1
+
+      expected = '\u001b[31m[error] » \u001b[39m\u001b[90mhello world\u001b[39m'
+      @acho.transport.store[0].should.be.equal expected
+      @acho.messages.error[0].should.be.equal 'hello world'
+
+  describe 'print', ->
+
+    it 'print a normal level message', ->
+      @acho.warn 'warn message'
+
+      @acho.transport.store.length.should.be.equal 1
+      expected = '\u001b[33m[warn] » \u001b[39m\u001b[90mwarn message\u001b[39m'
+      @acho.transport.store[0].should.be.equal expected
+
+    it 'change the color behavior',  ->
+      @acho.types.error.color = 'red bold'
+      @acho.push 'error', 'hello world'
+      @acho.print()
+
+      @acho.transport.store.length.should.be.equal 1
+      expected = '\u001b[31m\u001b[1m[error] » \u001b[22m\u001b[39m\u001b[90mhello world\u001b[39m'
+      @acho.transport.store[0].should.be.equal expected
+
+    it 'no ouptut messages out of the level',  ->
+      level = @acho.level
+      @acho.level = 'success'
+      @acho.verbose 'test of message'
+      @acho.transport.store.length.should.be.equal 0
+
+  describe 'customization', ->
     it 'default skin', ->
-      printLogs Acho level: 'silly', color: true
+      instance = Acho level: 'silly', color: true
+      printLogs instance
+      (instance.keyword?).should.be.false

     it 'specifying a keyword', ->
-      printLogs Acho level: 'silly', color: true, keyword: 'acho'
+      instance = Acho level: 'silly', color: true, keyword: 'acho'
+      printLogs instance
+      instance.keyword.should.be.equal 'acho'

     it 'specifying a special "symbol" keyword', ->
-      printLogs Acho level: 'silly', color: true, keyword: 'symbol'
+      instance = Acho level: 'silly', color: true, keyword: 'symbol'
+      printLogs instance
+      instance.keyword.should.be.equal 'symbol'

     it 'enabling diff between logs', (done) ->
       acho = Acho
@@ -72,4 +122,4 @@
         clearInterval warn
         clearInterval err
         done()
-      , 10000)
+      , 5000)


added new tests for setLevel

added new tests for setLevel

--- test/test.coffee
+++ test/test.coffee
@@ -33,15 +33,20 @@
 			"the result is the one-digits number left-padded with a zero": (topic) ->
 				assert.equal topic.padZeros(3,2),'03'

-		"when logging a message":
+		"when logging a message with setting the log level to 2 (warning)":
 			topic: () ->
+				logger.setLevel 2
 				config =
 					showMillis: true
 					prefix: "Test"
 				log = new logger config
-
-			"the log method doesn't throw an error": (topic) ->
+
+			"the warning method doesn't throw an error": (topic) ->
 				assert.doesNotThrow () ->
-					topic.info "test message"
+					topic.warn "test message"
+
+			"the info method returns -1": (topic) ->
+				assert.equal topic.info("test message"),-1
+

 .exportTo(module)


fix dependency install

fix dependency install

--- test/test.coffee
+++ test/test.coffee
@@ -8,7 +8,7 @@

 # make sure all tests with deps have them installed
 before (done) ->
-  util.project.install_dependencies(test_path, done)
+  util.project.install_dependencies('*', done)

 # remove output
 after ->


fix #23 : Uncaught TypeError: Cannot read property 'type' of undefined

--- lib/styleProvider.coffee
+++ lib/styleProvider.coffee
@@ -215,7 +215,8 @@
   getPropertyValueCompletions: (request) ->
     {bufferPosition, editor, prefix, scopeDescriptor} = request
     property = @getPreviousPropertyName(bufferPosition, editor)
-    values = @properties[property]?.values
+
+    return null unless @properties[property]

     if @types[@properties[property].type] # such as FONT
       candidateProperties = {};
@@ -223,6 +224,7 @@
         candidateProperties[item] = {}
       return this.getPropertyNameCompletions(request, candidateProperties)

+    values = @properties[property].values
     return null unless values?

     scopes = scopeDescriptor.getScopesArray()


fix undefined `p`

fix undefined `p`

--- lib/util/progress.coffee
+++ lib/util/progress.coffee
@@ -180,7 +180,8 @@
         @tooltip.hide_()
       # backwards iteration
       for i in [0...@stack.length].reverse()
-        table.appendChild @tableRowView @stack[i]
+        p = @stack[i]
+        table.appendChild @tableRowView p
         p.emitter.emit 'did-update-progress'

     div


bit more todo

bit more todo

--- lib/util/progress.coffee
+++ lib/util/progress.coffee
@@ -1,6 +1,8 @@
 # TODO: tooltip for each progress bar that displays the .msg
 # TODO: cleanup
 # TODO: styling
+# TODO: global progress bar
+# TODO: time estimates (here?)
 {Emitter} = require 'atom'
 {once} = require 'underscore-plus'


Set default language to English

Set default language to English

--- frontend/coffeescript/app/webform/views.coffee
+++ frontend/coffeescript/app/webform/views.coffee
@@ -33,7 +33,7 @@
         # languages:  []

         language_select: ( event ) ->
-          @modalView = new LanguageSelectModal( { current: @currentLanguage, view: this } )
+          @modalView = new LanguageSelectModal( { current: @current_language, view: this } )
           $('.modal').html( @modalView.render().el )
           @modalView.onAfterRender( $( '.modal' ) )
           @
@@ -46,7 +46,7 @@
             @currentQuestionIndex = 0
             @numberOfQuestions = document.flat_fields.length

-            @current_language = @set_current_language()
+            @change_language @set_current_language()

             @repopulateForm()
             @show_first_question()
@@ -59,7 +59,7 @@

         change_language: (language) ->
-          @currentLanguage = language
+          @current_language = language

           #iterate through all the questions and switch the text
           for question in document.flat_fields
@@ -83,17 +83,23 @@
         first_key: (dict) ->
           _.keys(dict)[0]

+        # Defaults to English if an English label is present, otherwise returns
+        # the first language it finds.
         set_current_language: ->
           first_field = document.flat_fields[0]

           if first_field.type is 'group'
             if typeof first_field.children[0].label is 'object'
+              # Default to English, otherwise return first label found
+              return 'English' if first_field.children[0].label['English']?
               return @first_key(first_field.children[0].label)
             else
               return first_field.children[0].label

           if first_field.type isnt 'group' and first_field.label?
             if typeof first_field.label is 'object'
+              # Default to English, otherwise return first label found
+              return 'English' if first_field.label['English']?
               return @first_key(first_field.label)
             else
               # doesn't have translations
@@ -107,9 +113,9 @@
           return label if typeof label is 'string'

           # otherwise label is a translation dict
-          if label[@currentLanguage]
+          if label[@current_language]
             # has translation so return translated label
-            return label[@currentLanguage]
+            return label[@current_language]
           else
             # has translations, but not the desired one so return first value.
             # for example:


Fixed some issues

Fixed some issues

--- app/locale/zh-HANS.coffee
+++ app/locale/zh-HANS.coffee
@@ -316,7 +316,7 @@
     tip_premature_optimization: "过早的优化是万恶之源。 - 高德纳"
     tip_brute_force: "拿不准时就用穷举法。 - Ken Thompson"
     tip_extrapolation: "世界上只有两类人：一类人能够根据不完整的数据进行推断……"
-    tip_superpower:  "编程是我们拥有的最接近超能力的技能"#"Coding is the closest thing we have to a superpower."
+    tip_superpower: "编程是我们拥有的最接近超能力的技能"

   game_menu:
     inventory_tab: "道具箱"
@@ -492,14 +492,14 @@
     forum_prefix: "如果你想发布任何公开的东西, 可以试试"
     forum_page: "我们的论坛"
     forum_suffix: ""
-    faq_prefix: "这里还有一个"#"There's also a"
+    faq_prefix: "这里还有一个"
     faq: "FAQ"
-    subscribe_prefix: "如果你需要帮助解决一个关卡，请"#"If you need help figuring out a level, please"
+    subscribe_prefix: "如果你需要帮助解决一个关卡，请"
     subscribe: "订阅CodeCombat"
     subscribe_suffix: "并且我们很乐意给你提供代码相关的帮助"
     subscriber_support: "既然你已经订阅了CodeCombat，我们将给你提供优先帮助"
     screenshot_included: "包含截屏"
-    where_reply: "我们应该回复谁？"#"Where should we reply?"
+    where_reply: "我们应该回复谁？"
     send: "反馈意见"
     contact_candidate: "联系参选人" # Deprecated
     recruitment_reminder: "用这张表格来联系你希望面试的求职者。但请记住如果成功雇佣，CodeCombat会收取与这位员工第一年工资的15%等值的佣金。佣金需在雇佣此员工时就付清并且在之后的90天内如果此员工离职则可退款。兼职，远程办公员工，合同工以及实习生都可免除此费用。" # Deprecated
@@ -589,7 +589,7 @@
     artisan_summary: "建立游戏关卡并分享给朋友们。那么就选择工匠职业来教其他人编程吧。"
     adventurer_title: "冒险家"
     adventurer_title_description: "(关卡测试人员)"
-    adventurer_summary:"提前一周免费获得我们最新的关卡（还有我们的订阅内容），并帮助我们在发布之前寻找程序错误" # "Get our new levels (even our subscriber content) for free one week early and help us work out bugs before our public release."
+    adventurer_summary: "提前一周免费获得我们最新的关卡（还有我们的订阅内容），并帮助我们在发布之前寻找程序错误"
     scribe_title: "文书"
     scribe_title_description: "(提示编辑人员)"
     scribe_summary: "好代码需要好文档，来自全世界数百万的玩家一起编写，编辑以及提高文档的可读性"


Update zh-HANS.coffee

Update zh-HANS.coffee

add punctuation

--- app/locale/zh-HANS.coffee
+++ app/locale/zh-HANS.coffee
@@ -73,7 +73,7 @@
     creating: "账户创建中……"
     sign_up: "注册"
     log_in: "登录"
-    social_signup: "或者你可以通过Facebook或G+注册"
+    social_signup: "或者，你可以通过Facebook或G+注册："

   home:
     slogan: "通过游戏学习 Javascript"


Fix typo

Fix typo

--- client/helpers.coffee
+++ client/helpers.coffee
@@ -27,7 +27,7 @@

     test = testExp route, regex, isPath

-    test = not test if invese
+    test = not test if inverse

     if test then className else false


small bug with image event rendering

small bug with image event rendering

--- src/integrations/dropbox/dropbox_server.coffee
+++ src/integrations/dropbox/dropbox_server.coffee
@@ -127,7 +127,7 @@
         row.push image
       else
         imageRows.push row
-        row = []
+        row = [image]
     if row.length
       imageRows.push row


Fixed setOutcome

Fixed setOutcome

--- server/api/setOutcome.coffee
+++ server/api/setOutcome.coffee
@@ -71,7 +71,7 @@
         await postToInformatics(req, res)
         success = await updateData(req, res)
     catch e
-        logger.info "Can't update informatics status " + href + " " + i + " will re-download"
+        logger.info "Can't update informatics status ", req.params.submitId
         logger.info e.message
     if not success and (req.body.result or req.body.comment)
         await storeToDatabase(req, res)


Dont use fakes in param lists

Dont use fakes in param lists

--- src/js/app/compilers/teascript/compiler.coffee
+++ src/js/app/compilers/teascript/compiler.coffee
@@ -7365,6 +7365,7 @@
 exports._operator = _operator
 exports._arguments = _arguments
 exports._terms = _terms
+exports._validTerms = _validTerms
 exports._snd = _snd
 exports._fst = _fst
 exports._labelName = _labelName


Hotfix sparql request <a>

Hotfix sparql request <a>

--- server/lib/abstracter.coffee
+++ server/lib/abstracter.coffee
@@ -13,23 +13,23 @@

         switch tok.type
             when 'who'
-                concrete.push s: '?person', p: 'a', o: 'foaf:Person'
+                concrete.push s: '?person', p: '<a>', o: 'foaf:Person'

             when 'when'
-                concrete.push s: '?instant', p: 'a', o: 'time:Instant'
+                concrete.push s: '?instant', p: '<a>', o: 'time:Instant'

             when 'phoneComLog'
                 pdta = '?log'
-                concrete.push s: '?log', p: 'a', o: 'pcrd:PhoneCommunicationLog'
+                concrete.push s: '?log', p: '<a>', o: 'pcrd:PhoneCommunicationLog'

             when 'phoneCall'
                 pdta = '?log'
-                concrete.push s: '?log', p: 'a', o: 'pcrd:PhoneCommunicationLog'
+                concrete.push s: '?log', p: '<a>', o: 'pcrd:PhoneCommunicationLog'
                 concrete.push s: '?log', p: 'pcrd:ComType', o: 'VOICE'

             when 'phoneText'
                 pdta = '?log'
-                concrete.push s: '?log', p: 'a', o: 'pcrd:PhoneCommunicationLog'
+                concrete.push s: '?log', p: '<a>', o: 'pcrd:PhoneCommunicationLog'
                 concrete.push s: '?log', p: 'pcrd:ComType', o: 'SMS'

             when 'myself'
@@ -46,10 +46,10 @@

             when 'allArticles'
                 pdta = '?receipt'
-                concrete.push s: '?receipt', p: 'a', o: 'rcp:Receipt'
+                concrete.push s: '?receipt', p: '<a>', o: 'rcp:Receipt'

             when 'article'
-                concrete.push s: '?receipt', p: 'a', o: 'rcpd:ReceiptDetail'
+                concrete.push s: '?receipt', p: '<a>', o: 'rcpd:ReceiptDetail'

             when 'float'
                 # Check if it's a price


lodchart: remove a call to console.log

lodchart: remove a call to console.log

--- lodchart/lodchart.coffee
+++ lodchart/lodchart.coffee
@@ -170,7 +170,6 @@
                      else
                        xpos -= 10
                        anchor = "end"
-                     console.log anchor
                      g.append("text")
                       .attr("x", xpos)
                       .attr("y", yscale(d.lod))


added md5 field

added md5 field

--- lixian.iced
+++ lixian.iced
@@ -87,6 +87,7 @@
             name: task.taskname
             url: task.lixian_url
             size: Number task.file_size
+            md5: task.verify
           ]
       if task.tasktype == 0
         tasks.push folder =
@@ -107,6 +108,7 @@
             name: item.title
             url: item.downurl
             size: Number item.filesize
+            md5: item.verify
     await @execute ((done)-> done null, document.cookie), defer e, cookie
     return cb e if e
     cb null, tasks: tasks, cookie: cookie


Update message plugin

Update message plugin

Fix message show at button some bugs

--- vendor/assets/javascripts/mice/message.coffee
+++ vendor/assets/javascripts/mice/message.coffee
@@ -30,25 +30,33 @@

     show: (message, duration) ->

-      duration = duration || @options.duration
+      if message
+        @$element = @$element || $('body').data('miclle-message-global')

-      @$element = @$element || $('body').data('miclle-message-global')
+        if @$element == `undefined`
+          @$element = $ $.fn.message.defaults.template
+          $('body').append(@$element).data('miclle-message-global', @$element)

-      if @$element == `undefined`
-        @$element = $ $.fn.message.defaults.template
-        $('body').append(@$element).data('miclle-message-global', @$element)
         if typeof message == 'string'
           @init( message: message )
         else
           @init(message)

-        @$element.removeClass('top').removeClass('bottom').addClass(@options.placement)
+        @$element.slideUp =>
+          @$element.removeClass('top').removeClass('bottom').addClass(@options.placement)

-      message = @options.message
+          message  = @options.message
+          duration = duration || @options.duration

-      @$element.children('.inner').html(message) if message
+          @$element.children('.inner').html(message) if message
+          @$element.slideDown => duration and setTimeout (=> @.hide()), duration

-      @$element.slideDown => duration and setTimeout (=> @.hide()), duration
+      else
+        message  = @options.message
+        duration = duration || @options.duration
+
+        @$element.children('.inner').html(message) if message
+        @$element.slideDown => duration and setTimeout (=> @.hide()), duration
       @

     hold: ->


removed reference to angular-iu

removed reference to angular-iu

--- vendor/assets/javascripts/qrious-ui.js.coffee
+++ vendor/assets/javascripts/qrious-ui.js.coffee
@@ -2,7 +2,6 @@
 #= require jquery/jquery
 #= require bootstrap/dist/js/bootstrap
 #= require angular/angular
-#= require angular-ui/build/angular-ui
 #= require_self

 $ ()->


Update news.coffee

Update news.coffee

--- server/materials/data/news.coffee
+++ server/materials/data/news.coffee
@@ -3,6 +3,9 @@

 export default allNews = () ->
     return news([
+        newsItem("Занятие 19 декабря в ВШЭ начнется в 14.15", String.raw"""
+            19 декабря занятие в ВШЭ начнется в 14.15.
+        """),
         newsItem("Запись обсуждения районной олимпиады", String.raw"""
             Выложена видеозапись занятия от 05.12.2021 про районную олимпиаду.
             Также можете посмотреть видеозапись аналогичного занятия 2019 г., в частности,


Fix typo

Fix typo

--- server/src/FrameController.coffee
+++ server/src/FrameController.coffee
@@ -104,7 +104,7 @@
                         thumb : hand.thumb.extended
                         indexFinger : hand.indexFinger.extended
                         middleFinger : hand.middleFinger.extended
-                        ringerFinger : hand.ringFinger.extended
+                        ringFinger : hand.ringFinger.extended
                         pinky : hand.pinky.extended
                     position: palmPosition
                     grabStrength : hand.grabStrength


Whitespace: code styling

Whitespace: code styling

--- servers/lib/server/helpers.coffee
+++ servers/lib/server/helpers.coffee
@@ -44,14 +44,19 @@
       callback false, account.profile.nickname

 findUsernameFromSession = (req, res, callback) ->
+
   {clientId} = req.cookies
+
   unless clientId?
-    process.nextTick -> callback null, no, ""
+
+    return process.nextTick -> callback null
+
   else
     koding.models.JSession.fetchSession clientId, (err, result)->
       if err
         console.error err
         callback err, undefined, ""
+
       else unless result?
         res.status(403).send 'Access denied!'
         callback null, false, ""
@@ -124,20 +129,30 @@

 isLoggedIn = (req, res, callback)->
+
   {JName} = koding.models
   findUsernameFromSession req, res, (err, isLoggedIn, username)->
+
+
     return callback null, no, {}  unless username
+
     JName.fetchModels username, (err, result)->
+
       return callback null, no, {}  unless result?

       { models } = result

       return callback null, no, {}  if err or not models?.first
+
       user = models.last
       user.fetchAccount "koding", (err, account)->
+
         if err or not account or account.type is 'unregistered'
+
           return callback err, no, account
+
         return callback null, yes, account
+

 saveOauthToSession = (oauthInfo, clientId, provider, callback)->
   {JSession}                       = koding.models


Fix talk moderation display bugs.  Closes #1385

- Renders destroyed comments correctly
- Removes invalid state parameter 'all'

--- app/talk/moderations.cjsx
+++ app/talk/moderations.cjsx
@@ -57,6 +57,7 @@
       {page: page, page_size: PAGE_SIZE, state: @props.query.state},
       if section then {section} else {}

+    delete moderationParams.state if moderationParams.state is 'all'
     auth.checkCurrent().then (user) => if user?
       talkClient.type('moderations').get(moderationParams)
         .then (moderations) =>
@@ -148,9 +149,15 @@

     }</PromiseRenderer>

+  moderatedComment: (moderation) ->
+    if moderation.destroyed_target
+      Promise.resolve moderation.destroyed_target
+    else
+      talkClient.type('comments').get moderation.target_id
+
   moderation: (moderation, i) ->

-      <PromiseRenderer promise={talkClient.type('comments').get(moderation.target_id)}>{(comment) =>
+      <PromiseRenderer promise={@moderatedComment moderation}>{(comment) =>

           {@comment(comment, moderation)}


log error if state file is truncacted

log error if state file is truncacted

--- app/coffee/SafeReader.coffee
+++ app/coffee/SafeReader.coffee
@@ -1,4 +1,5 @@
 fs = require "fs"
+logger = require "logger-sharelatex"

 module.exports = SafeReader =

@@ -21,4 +22,6 @@
 			fs.read fd, buff, 0, buff.length, 0, (err, bytesRead, buffer) ->
 				return callbackWithClose(err) if err?
 				result = buffer.toString(encoding, 0, bytesRead)
+				if bytesRead is size
+					logger.error file:file, size:size, bytesRead:bytesRead, "file truncated"
 				callbackWithClose(null, result)


Call getSelectedItemView from getSelectedItem

--- src/select-list-view.coffee
+++ src/select-list-view.coffee
@@ -204,7 +204,7 @@
   #
   # Returns a model item.
   getSelectedItem: ->
-    @getSelectedView().data('select-list-item')
+    @getSelectedItemView().data('select-list-item')

   confirmSelection: ->
     item = @getSelectedItem()


Attempt at smart HTML pasting

Attempt at smart HTML pasting

--- client/message.coffee
+++ client/message.coffee
@@ -594,6 +594,23 @@
                       "coauthor:#{id}"
             cmDrop e
           editor.setOption 'dragDrop', true
+
+          html = null
+          editor.on 'paste', (cm, e) ->
+            console.log e.clipboardData.types
+            if 'text/html' in e.clipboardData.types
+              html = e.clipboardData.getData 'text/html'
+              html = html.replace /<!--.*?-->/g, ''
+              html = html.replace /<\/?(html|head|body|meta|span)\b[^<>]*>/ig, ''
+              html = html.replace /<b\s+style="font-weight:\s*normal[^<>]*>(.*?)<\/b>/ig, '$1'
+              html = html.replace /<(p|li) dir="ltr"/ig, '<$1'
+              html = html.replace /<(p|li|ul) style="[^"]*"/ig, '<$1'
+            else
+              html = null
+          editor.on 'beforeChange', (cm, change) ->
+            if change.origin == 'paste' and html?
+              change.text = html.split /\r|\n|\r\n/
+
         when 'ace'
           editor.textInput.getElement().setAttribute 'tabindex', 1 + 20 * ti.count + 19
           #editor.meteorData = @  ## currently not needed, also dunno if works


New protected UI (fix #530), tooltips for Edit/Stop Editing

--- client/message.coffee
+++ client/message.coffee
@@ -529,12 +529,6 @@
       <>
         {' '}
         Minimized
-      </>
-    }
-    {if message.protected
-      <>
-        {' '}
-        Protected
       </>
     }

@@ -1942,6 +1936,7 @@
     edit: canEdit message._id
     reply: canReply message
     super: canSuper message.group
+    becomeSuper: canSuper message.group, false
   , [message._id]
   ref = useRef()
   messageBodyRef = useRef()
@@ -2367,18 +2362,72 @@
             <>
               <MessageActions message={message} can={can} editing={editing} tabindex0={tabindex0}/>
               {if editing
-                if editStopping
-                  <button className="btn btn-info editButton disabled" tabIndex={tabindex0+8} title="Waiting for save to complete before stopping editing...">Stop Editing</button>
-                else
-                  <button className="btn btn-info editButton" tabIndex={tabindex0+8} onClick={onEdit}>Stop Editing</button>
-              else
-                if can.edit and not folded
-                  <>
-                    {if message.file
-                      <MessageReplace _id={message._id} group={message.group} tabindex={tabindex0+7}/>
+                <OverlayTrigger flip overlay={(props) ->
+                  <Tooltip {...props}>
+                    {if editStopping
+                      <>Waiting for message to save before stopping editing...</>
+                    else
+                      <>
+
Close editor and mark this version as "finished". (Your edits are already saved, and users with access to this message can already see your edits.)

+                        {unless message.published
+
Don't forget to publish your message when you're ready for others to see it!

+                        }
+                        {if message.deleted
+
Undelete your message if you want others to see it.

+                        }
+                      </>
                     }
-                    <button className="btn btn-info editButton" tabIndex={tabindex0+8} onClick={onEdit}>Edit</button>
-                  </>
+                  </Tooltip>
+                }>
+
+                    <button className="btn btn-info editButton" onClick={onEdit}
+                     disabled={editStopping} tabIndex={tabindex0+8}>
+                      Stop Editing
+                    </button>
+
+                </OverlayTrigger>
+              else unless folded
+                <>
+                  {if message.file and can.edit
+                    <MessageReplace _id={message._id} group={message.group} tabindex={tabindex0+7}/>
+                  }
+                  <OverlayTrigger flip overlay={(props) ->
+                    <Tooltip {...props}>
+                      {if can.edit
+

+                          Start editing this message (possibly with other users).
+                          {unless amCoauthor message, user
+                            <>
Changes will automatically make you a coauthor.</>
+                          }
+

+                      else unless messageRoleCheck message.group, message, 'edit', user
+

+                          You do not have edit permissions in this group/thread.
+

+                      }
+                      {if message.protected
+
Message is protected, so edits are restricted to coauthors and superusers.

+                      }
+                      {if can.becomeSuper and not can.edit
+
Become Superuser (or type s) to edit this message.

+                      }
+                    </Tooltip>
+                  }>
+
+                      <button className="btn btn-info editButton"
+                       tabIndex={tabindex0+8} onClick={onEdit}
+                       disabled={not can.edit}>
+                        Edit
+                        {if message.protected
+                          <>
+                            {' '}
+
+                          </>
+                        }
+                      </button>
+
+                  </OverlayTrigger>
+                </>
               }
             </>
           }


Fix sex label not updating correctly

Fix sex label not updating correctly

--- app/views/dragon-view.coffee
+++ app/views/dragon-view.coffee
@@ -16,6 +16,7 @@
   setDragon: (@dragon)->
     @_updateClasses()
     @_updateCharacteristics()
+    @_updateSexLabel()

   setSexVisible: (@showSex) ->
     @_updateVisibility()
@@ -28,6 +29,7 @@
       if evt.detail.dragon is @dragon
         @_updateClasses()
         @_updateCharacteristics()
+        @_updateSexLabel()

   _updateClasses: ->
     @phenoView.setAttribute('class', '')
@@ -55,6 +57,9 @@

     @characteristicList.innerHTML = out.join('')

+  _updateSexLabel: ->
+    @sexLabel.innerHTML = if @dragon.sex is BioLogica.MALE then "male" else "female"
+
   _injectHtml: ->
     @phenoView = document.createElement 'div'
     @phenoView.classList.add 'dragon-view'
@@ -68,12 +73,13 @@
     @sexLabel = document.createElement 'div'
     @sexLabel.classList.add 'sex-label'
     @sexLabel.classList.add 'hidden' unless @showSex
-    @sexLabel.innerHTML = if @dragon.sex is BioLogica.MALE then "male" else "female"
     @div.appendChild @sexLabel

     @characteristicList = document.createElement 'div'
     @characteristicList.classList.add 'characteristic-list'
     @characteristicList.classList.add 'hidden' unless @showCharacteristics
+    @div.appendChild @characteristicList
+
+    @_updateSexLabel()
     @_updateCharacteristics()

-    @div.appendChild @characteristicList


sync2: remove task hashtags in history viewer since don't work right now

--- src/smc-webapp/tasks.coffee
+++ src/smc-webapp/tasks.coffee
@@ -120,6 +120,7 @@
         @element.find(".salvus-tasks-loading").remove()
         @element.find(".salvus-task-empty-trash").remove()
         @element.find(".salvus-tasks-action-buttons").remove()
+        @element.find(".salvus-tasks-hashtags-row").remove()  # TODO: only because they don't work in viewer mode; for later.

     init_syncdb: (cb) =>
         synchronized_db


Fix error messages

Fix error messages

--- emulation/atom-emulator.coffee
+++ emulation/atom-emulator.coffee
@@ -15,7 +15,7 @@
   else if Array.isArray(obj)
     return new (atom.Range)(obj[0], obj[1])
   else
-    console.error 'rangeFromObj parameter is Range nor Array: '
+    console.error 'Range.fromObj parameter is Range nor Array: '
     console.dir obj

 class Point
@@ -33,7 +33,7 @@
   else if Array.isArray(obj)
     return new (atom.Point)(obj[0], obj[1])
   else
-    console.error 'fromObj parameter is Point nor Array: '
+    console.error 'Point.fromObj parameter is Point nor Array: '
     console.dir obj

 class TextManager


Fix empty lines in Buffer.setText

Fix empty lines in Buffer.setText

--- emulation/atom-emulator.coffee
+++ emulation/atom-emulator.coffee
@@ -180,7 +180,11 @@
   setText: (txt) ->
     @$editableDiv.empty()
     for line in txt.split '\n'
-      lineDiv = $('<div>').text line
+      lineDiv = $('<div>')
+      if line.length > 0
+        lineDiv.text line
+      else
+        lineDiv.append $('<br>')
       @$editableDiv.append lineDiv

   getSelectionRange: ->


do not show the prefix when loading a draft

Signed-off-by: Jessica González <52329a27ea5670aeec0efeeb0773bf1b4f1affa1@missallsunday.com>

--- src/summernoteDrafts.coffee
+++ src/summernoteDrafts.coffee
@@ -61,6 +61,7 @@

       @destroy = =>
         ui.hideDialog @$dialog
+        @$dialog.remove()
         return

       @show = =>
@@ -84,7 +85,7 @@
           sDate: isoDate
           body : body
         alert lang.saved
-        ui.hideDialog @$dialog
+        @destroy()

         return
       return
@@ -104,7 +105,8 @@
     for key, draft of drafts
       do ->
         fDate = if options.sDrafts.dateFormat and typeof options.sDrafts.dateFormat == 'function' then options.sDrafts.dateFormat(draft.sDate) else draft.sDate
-        htmlList += "
#{draft.name} - #{fDate}#{lang.deleteDraft}
"
+        fName = draft.name.replace "#{options.sDrafts.storePrefix}-", ""
+        htmlList += "
#{fName} - #{fDate}#{lang.deleteDraft}
"

     context.memo 'button.sDraftsLoad', () ->
       button = ui.button
@@ -132,10 +134,12 @@

     @destroy = =>
       ui.hideDialog @$dialog
+      @$dialog.remove()
       return

     @show = =>
       ui.showDialog @$dialog
+      self = @
       $selectedDraft = @$dialog.find '.note-draft'
         .click (e) ->
           e.preventDefault
@@ -152,8 +156,7 @@
           else
             alert lang.noDraft

-          @destroy()
-          @$dialog.remove()
+          self.destroy()
           false

       $deleteDraft = @$dialog.find 'a.delete-draft'


Remove unused argument

Remove unused argument

--- src/trix/models/text.coffee
+++ src/trix/models/text.coffee
@@ -27,7 +27,7 @@
     @copyWithPieceList @pieceList

   copyWithPieceList: (pieceList) ->
-    new @constructor pieceList.consolidate().toArray(), @attributes
+    new @constructor pieceList.consolidate().toArray()

   appendText: (text) ->
     @insertTextAtPosition(text, @getLength())


Normalize pathname when getting file: resource

--- src/zombie/resources.coffee
+++ src/zombie/resources.coffee
@@ -12,6 +12,7 @@
 HTTP = require("http")
 HTTPS = require("https")
 FS = require("fs")
+Path = require("path")
 QS = require("querystring")
 URL = require("url")
 VM = process.binding("evals")
@@ -144,7 +145,7 @@
       # file system rather than getting node's http (which handles file://
       # poorly) involved.
       if url.protocol == "file:"
-        FS.readFile url.pathname, (err, data) =>
+        FS.readFile Path.normalize(url.pathname), (err, data) =>
           # Fallback with error -> callback
           if err
             window.browser.log -> "Error loading #{URL.format(url)}: #{err.message}"


Minor fixes and improvements

Minor fixes and improvements

--- gruntfile.coffee
+++ gruntfile.coffee
@@ -30,11 +30,11 @@
       options:
         livereload: true
       jade:
-        files: ["views/**/*"],                 tasks: ["jade"]
+        files: ["views/**/*"], tasks: ["jade"]
       coffee:
-        files: "assets/**/*.coffee",           tasks: ["coffeelint:app", "coffee", "uglify"]
+        files: "assets/**/*.coffee", tasks: ["coffeelint:app", "coffee", "uglify"]
       coffee_server:
-        files: "server/**/*.coffee",           tasks: ["coffeelint:server"]
+        files: "server/**/*.coffee", tasks: ["coffeelint:server"]
       stylus:
         files: "assets/stylesheets/**/*.styl", tasks: ["stylus", "cssmin"]

@@ -55,7 +55,7 @@
         files  : [
           expand: true
           cwd   : "assets/stylesheets"
-          src   :  "**/*.styl"
+          src   : ["**/*.styl", "!**/vendor.styl"]
           dest  : "public/css"
           ext   :  ".css"]

@@ -90,15 +90,15 @@
       server   : ["server/**/*.coffee"]
       gruntfile: "gruntfile.coffee"
       options  :
-        max_line_length:     level: "ignore"
+        max_line_length    : level: "ignore"
         no_throwing_strings: level: "warn"

     clean:
-      js    : ["public/js",  "public/<%=pkg.name%>.js", "public/<%=pkg.name%>.min.js"]
+      js    : ["public/js", "public/<%=pkg.name%>.js", "public/<%=pkg.name%>.min.js"]
       css   : ["public/css", "public/<%=pkg.name%>.min.css"]
       views : ["public/views"]
       images: ["public/img"]
-      prod  : ["public/js",  "public/<%=pkg.name%>.js"]
+      prod  : ["public/js", "public/<%=pkg.name%>.js"]

     copy:
       images:
@@ -148,7 +148,7 @@
   grunt.registerTask "default",     "Main Grunt. Executes all tasks.", [
     "build", "coffeelint:server", "concurrent"]

-  grunt.registerTask "launch",     "Main Grunt. Executes all tasks.", [
+  grunt.registerTask "launch",     "Main Grunt. Executes all tasks.",  [
     "build", "coffeelint:server", "env:prod", "nodemon"]

   grunt.registerTask "build",       "Compiles all files of project.",  [


here is some pagination

here is some pagination

--- static/coffee/models.coffee
+++ static/coffee/models.coffee
@@ -41,6 +41,7 @@

     getInitialState: -> {
         messageCount: 'Loading...'
+        pagination: ''
     }

     componentDidMount: ->
@@ -52,10 +53,12 @@
             }
             cmrMailbox: @props.name
         }, (data) =>
-            # TODO
-            #if @props.name == @props.currentMailbox
-            #    messageList.setState({ pages: Math.ceil(data / 10) })
-            @setState({messageCount: data})
+            @setState({
+                messageCount: data
+                pagination: FM.Pagination({
+                    total: Math.ceil(data / 10)
+                })
+            })
         )

     render: ->
@@ -84,7 +87,9 @@
                     id: safeID
                     className: 'panel-collapse collapse'
                 },
-                React.DOM.div({className: 'panel-body'}, messageList))
+                React.DOM.div({className: 'panel-body'}, messageList)
+                React.DOM.div({className: 'text-center'}, @state.pagination)
+            )
         )
 })

@@ -135,13 +140,6 @@
                 React.DOM.tbody(null,
                     messageNodes
                 )
-            ),
-            React.DOM.div({className: "text-center"},
-                FM.Pagination({
-                    total: @state.pages
-                    currentPage: @state.currentPage
-                    loadingPage: @state.loadingPage
-                })
             )
         );


Remove redundant `/notification` path component

--- services/notifications/app.coffee
+++ services/notifications/app.coffee
@@ -24,7 +24,7 @@
 app.get '/user/:user_id', controller.getUserNotifications
 app.del '/user/:user_id/notification/:notification_id', controller.removeNotificationId
 app.del '/user/:user_id', controller.removeNotificationKey
-app.del '/notification/key/:key', controller.removeNotificationByKeyOnly
+app.del '/key/:key', controller.removeNotificationByKeyOnly

 app.get '/status', (req, res)->
 	res.send('notifications sharelatex up')


fixed avatar count

fixed avatar count

--- src/smc-webapp/profile.cjsx
+++ src/smc-webapp/profile.cjsx
@@ -307,7 +307,7 @@
         if all_users.length > num_users_to_display
             rest =
                 <span style={fontSize:"small", cursor:"pointer", marginBottom:"4px", marginRight:"10px"}>
-                    {"+ #{all_users.length-4}"}
+                    {"+ #{all_users.length-5}"}
                 </span>
             users_to_display.push <OverlayTrigger
                     rootClose = true


store FOG value in state while editing

store FOG value in state while editing

--- client/lanes/screens/SystemSettings.cjsx
+++ client/lanes/screens/SystemSettings.cjsx
@@ -21,15 +21,21 @@
                 onChange={_.partial(@onChange, 'storage_dir')} />
         </LC.FieldWrapper>

+    fogValue: ->
+        @state.fogValue or
+            JSON.stringify(@config.settings.lanes.fog_credentials, null, 2)
+    setFog: (ev) ->
+        @setState(fogValue: ev.target.value)
+
     renderFogOptions: ->
-        value = JSON.stringify(@config.settings.lanes.fog_credentials, null, 2)
-        <LC.FieldWrapper label='Fog Options' sm=9 {...@props} value={value}>
-            <BS.Input type="textarea" placeholder="FOG options (as JSON)" value={value}
-                onChange={_.partial(@onChange, 'fog_credentials')} />
-        </LC.FieldWrapper>
+        <LC.TextArea sm=9 name='fog' placeholder="FOG options (as JSON)" model={@config}
+            getValue={@fogValue} onChange={@setFog} />

     saveConfig: ->
         comp.onBeforeSave?() for id, comp of @refs
+        if @state.fogValue
+            try
+                @config.settings.lanes.fog_credentials = JSON.parse(@state.fogValue)
         @config.save(saveAll: true)
         comp.onSave?() for id, comp of @refs


:bug:Only show current student's code in student details

--- app/views/teachers/TeacherStudentView.coffee
+++ app/views/teachers/TeacherStudentView.coffee
@@ -85,7 +85,7 @@
       solution = level.getSolutions().find((s) => s.language is 'html') unless solution
       @levelSolutionMap[level.get('original')] = solution.source if solution
     @levelStudentCodeMap = {}
-    for session in @sessions.models
+    for session in @sessions.models when session.get('creator') is @studentID
       @levelStudentCodeMap[session.get('level').original] = session.get('code')?['hero-placeholder']?['plan']

   onChangeCourseChart: (e)->
@@ -298,7 +298,6 @@
     for session in @sessions.models when session.get('creator') is @studentID
       @levelSessionMap[session.get('level').original] = session

-
     # Create mapping of level to student progress
     @levelProgressMap = {}
     for versionedCourse in @classroom.getSortedCourses() or []


Use sublime key binding

Use sublime key binding

--- keymaps/bracket-matcher.cson
+++ keymaps/bracket-matcher.cson
@@ -1,2 +1,2 @@
 '.editor':
-  'ctrl-j': 'editor:go-to-matching-bracket'
+  'ctrl-m': 'editor:go-to-matching-bracket'


Use Editor.begin/abortTransaction()

Use Editor.begin/abortTransaction()

--- lib/autocomplete-view.coffee
+++ lib/autocomplete-view.coffee
@@ -90,12 +90,12 @@
   cancelled: ->
     super

-    @editor.abort()
+    @editor.abortTransaction()
     @editor.setSelectedBufferRange(@originalSelectionBufferRange)
     rootView.focus() if @miniEditor.isFocused

   attach: ->
-    @editor.transact()
+    @editor.beginTransaction()

     @aboveCursor = false
     @originalSelectionBufferRange = @editor.getSelection().getBufferRange()


updated docs

updated docs

--- lib/datatables.client.coffee
+++ lib/datatables.client.coffee
@@ -3,28 +3,25 @@

 # ## Initialization

-# ### rendered()
+# ##### rendered()
 #   * When the component is first rendered datatables is initialized
 #   * The `this` context for all of the component methods is `templateInstance.__component__`
-
 Template.dataTable.rendered = ->
   templateInstance = @
   instantiatedComponent = templateInstance.__component__
   instantiatedComponent.log "rendered", @
   instantiatedComponent.initialize()

-# ### destroyed()
+# ##### destroyed()
 #   * Currently nothing is done when the component is destroyed
 #   * This may change
-
 Template.dataTable.destroyed = ->
   @log "destroyed"

-# ### initialize()
+# ##### initialize()
 #   * Set the initial table properties from the component declaration
 #   * Initialize the jQuery DataTables object
 #   * Initialize other third parties if they exist ( plugins, select2, etc. )
-
 Template.dataTable.initialize = ->
   @prepareQuery()
   @prepareCollection()
@@ -44,29 +41,24 @@
 #   * The default options are listed below.
 #   * The default options can changed by setting `Template.dataTable.defaultOptions.yourDumbProperty`

-# #### [DataTables Options Full Reference](https://datatables.net/ref)
+# ##### [DataTables Options Full Reference](https://datatables.net/ref)
 #   * All options passed through the options parameter will be merged with the default options.
 #   * e.g. `_.defaults options, defaultOptions`
-
 Template.dataTable.defaultOptions =
   bJQueryUI: false
   bAutoWidth: true
   bDeferRender: true
   sPaginationType: "full_numbers"
-
-  # #### Bootstrap 3 Markup
+  # ##### Bootstrap 3 Markup
   #   * You can change this by setting `Template.dataTable.defaultOptions.sDom` property.
   #   * For some example Less / CSS styles check out [luma-ui's dataTable styles](https://github.com/LumaPictures/luma-ui/blob/master/components/dataTables/dataTables.import.less)
-
   sDom: "<\"datatable-header\"fl><\"datatable-scroll\"rt><\"datatable-footer\"ip>"
   oLanguage:
     sSearch: "_INPUT_"
     sLengthMenu: "Show : _MENU_"
-
-    # #### Loading Message
+    # ##### Loading Message
     #   * Set `oLanguage.sProcessing` to whatever you want, event html
     #   * Haven't tried a Meteor template yet, could be fun!
-
     sProcessing: "Loading"
     oPaginate:
       sFirst: "First"
@@ -78,20 +70,21 @@
   aaData: []
   aoColumns: []

+# ##### setOptions()
 Template.dataTable.setOptions = ( options ) ->
   Match.test options, Object
   @setData 'options', options
   @log "options:set", options

+# ##### getOptions()
 Template.dataTable.getOptions = ->
   return @getData().options or @presetOptions() or false

-# ### prepareOptions()
+# ##### prepareOptions()
 #   * Prepares the datatable options object by merging the options passed in with the defaults
 #   * If the componet was declared with a collection and a query it is setup as a reactive datatable
 #   * `options.sAjaxSource` is currently useless, but is passed into `fnServerData` by datatables
 #   * binds the datatables `fnServerData` server callback to this component instance
-
 Template.dataTable.prepareOptions = ->
   options = @getOptions() or {}
   options.aaData = @getRows() or []
@@ -106,15 +99,16 @@
 # #### `selector` String ( required )
 #   * The table selector for the dataTable instance you are creating.
 #   * Needs to be unique in the page scope or you will get datatable mulit-render error
-
 Template.dataTable.setSelector = ( selector ) ->
   Match.test selector, String
   @setData 'selector', selector
   @log 'selector:set', selector

+# ##### getSelector()
 Template.dataTable.getSelector = ->
   return @getData().selector or false

+# ##### prepareSelector()
 Template.dataTable.prepareSelector = ->
   unless @getSelector()
     @setSelector "datatable-#{ @getGuid() }"
@@ -123,14 +117,17 @@
 #   * this is a static datasource
 #   * can be used to display static data, or reactive client side data

+# ##### setRows()
 Template.dataTable.setRows = ( rows ) ->
   Match.test rows, Object
   @setData 'rows', rows
   @log 'rows:set', rows

+# ##### preprareRows()
 Template.dataTable.prepareRows = ->
   return

+# ##### getRows()
 Template.dataTable.getRows = ->
   if @getDataTable()
     return @getDataTable().fnSettings().aoData or false
@@ -138,7 +135,6 @@

 # ##### getRowIndex()
 #   * gets the datatable index of a row by mongo id
-
 Template.dataTable.getRowIndex = ( _id ) ->
   index = false
   counter = 0
@@ -155,6 +151,7 @@
 #   * This is where to map object properties to columns and their headers
 #   * This is where you define custom templates for rendering data in cells

+# ##### setColumns()
 Template.dataTable.setColumns = ( columns ) ->
   Match.test columns, Array
   @setData 'columns', columns
@@ -163,7 +160,6 @@
 # ##### prepareColumns()
 #   * adds _id as a hidden column by default
 #   * sets a default cell render function for every column
-
 Template.dataTable.prepareColumns = ->
   columns = @getColumns() or []
   columns.push
@@ -176,7 +172,6 @@

 # ##### setDefaultCellValue()
 #   * defaults all cells to "" if undefined
-
 Template.dataTable.setDefaultCellValue = ( column ) ->
   Match.test column.mData, String
   Match.test column.sTitle, String
@@ -184,6 +179,7 @@
     column.mRender = ( dataSource, call, rawData ) ->
       rawData[ column.mData ] ?= ""

+# ##### getColumns()
 Template.dataTable.getColumns = ->
   return @getData().columns or false

@@ -194,7 +190,6 @@

 # ##### Default Table Template
 #   * The default table template is defined in datatables.html
-
 Template.dataTable.defaultTemplate = 'default_table_template'

 # ##### chooseTemplate Helper
@@ -202,7 +197,6 @@
 #   * Set table template to default if no template name is passed in
 #   * If the template is defined return it
 #   * Otherwise return the default template
-
 Template.dataTable.chooseTemplate = ( table_template = null ) ->
   table_template ?= Template.dataTable.defaultTemplate
   if Template[ table_template ]
@@ -213,17 +207,18 @@
 # #### `collection` Meteor Collection ( required )
 #   * This is the collection that house the documents your datatable is displaying
 #   * This collection must be defined on both the client and the server
-
 Template.dataTable.setCollection = ( collection ) ->
   Match.test collection, Object
   @setData 'collection', collection
   @log 'collection:set', collection

+# ##### setCountCollection()
 Template.dataTable.setCountCollection = ( collection ) ->
   Match.test collection, Object
   @setData 'countCollection', collection
   @log 'collection:count:set', collection

+# ##### prepareCollection()
 Template.dataTable.prepareCollection = ->
   @prepareCountCollection()
   return
@@ -231,12 +226,12 @@
 # #### `subscription` String ( required )
 #   * The name of the subscription your datatables is paging, sorting, and filtering
 #   * This must be a datatable compatible publication ( for more info see Publishing below )
-
 Template.dataTable.setSubscription = ( subscription ) ->
   Match.test subscription, Object
   @setData 'subscription', subscription
   @log 'subscription:set', subscription

+# ##### setSubscriptionOptions()
 Template.dataTable.setSubscriptionOptions = ->
   options =
     skip: @getTableState().iDisplayStart
@@ -248,7 +243,6 @@
 # ##### setSubscriptionHandle()
 #   * subscribes to the dataset for the current table state
 #   * stores the handle for later access
-
 Template.dataTable.setSubscriptionHandle = ->
   if @getSubscriptionHandle()
     @getSubscriptionHandle().stop()
@@ -259,15 +253,6 @@
 # ##### setSubscriptionAutorun()
 #   * creates a reactive computation that runs when the subscription is `ready()`
 #   * sets up local cursor ( identical to server except no skip )
-#   * fetches data from collection and passes it to datatables
-#     * does this by calling the `fnCallback()` passed to `fnServerData()`
-#     * sends an unaltered copy of `sEcho` ( draw count ) to datatables
-#     * gets total docs, before filtering i.e. the total number of records in the database
-#     * gets total records, after filtering i.e. the total number of records after filtering has been applied
-#       * both of these counts come from the `DataTableSubscriptionCount` collection on the client
-#       * `DataTableSubscriptionCount` is populated reactively by the DataTables publication for this table
-#       * The selector used to retrieve the counts is the subscription name and + '_filtered'
-
 Template.dataTable.setSubscriptionAutorun = ( fnCallback ) ->
   Match.test fnCallback, Object
   if @getSubscriptionAutorun()
@@ -280,8 +265,16 @@
         limit: @getTableState().iDisplayLength
         sort: @getTableState().sort
       @setCursor @getCollection().find @getTableState().query, cursorOptions
+      #   * fetches data from collection and passes it to datatables
+      #     * does this by calling the `fnCallback()` passed to `fnServerData()`
+      #     * sends an unaltered copy of `sEcho` ( draw count ) to datatables
+      #     * gets total docs, before filtering i.e. the total number of records in the database
+      #     * gets total records, after filtering i.e. the total number of records after filtering has been applied
       aaData = @getCursor().fetch()
       @log 'fnServerData:aaData', aaData
+      #     * both of these counts come from the `DataTableSubscriptionCount` collection on the client
+      #     * `DataTableSubscriptionCount` is populated reactively by the DataTables publication for this table
+      #     * The selector used to retrieve the counts is the subscription name and + '_filtered'
       fnCallback
         sEcho: @getTableState().sEcho
         iTotalRecords: @getTotalCount()
@@ -290,15 +283,19 @@
   @setData 'autorun', autorun
   @log 'subscription:autorun:set', autorun

+# ##### getSubscription()
 Template.dataTable.getSubscription = ->
   return @getData().subscription or false

+# ##### getSubscriptionOptions()
 Template.dataTable.getSubscriptionOptions = ->
   return @getData().subscriptionOptions or false

+# ##### getSubscriptionHandle()
 Template.dataTable.getSubscriptionHandle = ->
   return @getData().handle or false

+# ##### getSubscriptionAutorun()
 Template.dataTable.getSubscriptionAutorun = ->
   return @getData().autorun or false

@@ -307,15 +304,18 @@
 #   * You should attempt to narrow your selection as much as possbile to improve performance
 #   * The default query is `{}`

+# ##### setQuery()
 Template.dataTable.setQuery = ( query ) ->
   Match.test query, Object
   @setData 'query', query
   @log 'query:set', query

+# ##### prepareQuery()
 Template.dataTable.prepareQuery = ->
   unless @getQuery()
     @setQuery {}

+# ##### getQuery()
 Template.dataTable.getQuery = ->
   return @getData().query or false

@@ -330,9 +330,11 @@
 #     * `options` logs the datatables options for that instantiated component
 #     * `fnServerData` logs each request to the server by the component

+# ##### isDebug()
 Template.dataTable.isDebug = ->
   return @getData().debug or false

+# ##### log()
 Template.dataTable.log = ( message, object ) ->
   if @isDebug()
     if message.indexOf( @isDebug() ) isnt -1 or @isDebug() is "true"
@@ -340,15 +342,14 @@

 # ## Querying MongoDB

-# ### mapTableState()
+# ##### mapTableState()
 #   * Take the `aoData` parameter of `fnServerData` and map it into a more usable object
 #   * Convert aoData to key -> value pairs
-
 Template.dataTable.mapTableState = ( aoData ) ->
   aoData = @arrayToDictionary aoData, 'name'
   @log 'mapTableState:aoData', aoData

-  # #### tableState
+  # ##### tableState
   #   * `aoData.sEcho` is a request counter, incremented on every server call
   #   * `aoData.columns` contains the property meta data for each column
   #   * `aoData.iColumns` is the number of columns being displayed
@@ -356,7 +357,6 @@
   #   * `aoData.iDisplayLength` is the number of rows being displayed
   #   * `aoData.iDisplayStart` is the number of rows to skip for pagination
   #   * `aoData.sSearch` is the datatables search input value
-
   tableState =
     sEcho: aoData.sEcho.value or 1
     bRegex: aoData.bRegex.value or false
@@ -368,14 +368,13 @@
     iDisplayStart: aoData.iDisplayStart.value or 0
     sSearch: aoData.sSearch.value or ""

-  # #### getDataProp()
+  # ##### getDataProp()
   #   * function scope helper for getting `aoData` properties
-
   getDataProp = ( key, index ) ->
     key = "#{ key }_#{ index }"
     return aoData[ key ].value

-  # #### mapColumns()
+  # ##### mapColumns()
   #   * iterator for setting up columns
   #   * create an element for each column
   #   * `mDataProp` is the field name
@@ -383,7 +382,6 @@
   #   * `bSearchable` is a boolean used to determine which fields are searchable
   #   * `bSortable` is a boolean used to determine which fields are sortable
   #   * `sSearch` contains the column search string if column filters are setup
-
   mapColumns = ( index ) ->
     tableState.columns[ getDataProp 'mDataProp', index ] =
       mDataProp: getDataProp 'mDataProp', index
@@ -393,7 +391,7 @@
       sSearch: getDataProp 'sSearch', index
   mapColumns index for index in [ 0..( tableState.iColumns - 1 ) ]

-  # #### mapQuery()
+  # ##### mapQuery()
   #   * If there is a global filter the filter query is merged with the tables default query
   #   * `mapQuery()` is actually just an iterator
   #   * The filter query is initialized as an `$or` of all the searchable columns against the search regex
@@ -403,7 +401,6 @@
   #   * If the base query is for all records in the collection, the filter query is the only query run
   #   * If the base query is already filter the collection, the filter query is run as an `$and` against it
   #   * e.g. all documents must pass both the table query and search query
-
   if tableState.sSearch isnt ""
     searchQuery = $or: []
     mapQuery = ( key, property ) ->
@@ -425,7 +422,7 @@
         ]
   else tableState.query = @getQuery()

-  # #### mapSortOrder()
+  # ##### mapSortOrder()
   #   * Another column iterator
   #   * Only runs if columns are being sorted
   #   * Sets sort direction for each sorted field, allowing multi column sort
@@ -441,29 +438,29 @@
     mapSortOrder sortIndex for sortIndex in [ 1..tableState.iSortingCols ]
   return tableState

+# ##### setTableState()
 Template.dataTable.setTableState = ( aoData ) ->
   Match.test aoData, Object
   tableState = @mapTableState aoData
   @setData 'tableState', tableState
   @log 'tableState:set', tableState

+# ##### getTableState()
 Template.dataTable.getTableState = ->
   return @getData().tableState or false

-# ### fnServerData()
+# ##### fnServerData()
 #   * The callback for every dataTables user / reactivity event
 #   * `sSource` is the currently useless `sAjaxProp` from the options
 #   * `aoData` is an array of objects provided by datatables reflecting its current state
 #   * `fnCallback` is the function that will be called when the server returns a result
 #   * `oSettings` is the datatables settings object
-
 Template.dataTable.fnServerData = ( sSource, aoData, fnCallback, oSettings ) ->
   @setTableState aoData
   @setSubscriptionOptions()
   @setSubscriptionHandle()
   @setSubscriptionAutorun fnCallback
-
-  # #### Wat?
+  # ##### Wat?
   #   * `setTableState()` parses aoData and creates a usable table state object
   #   * `setSubscriptionOptions()` turns the table state into a MongoDB query options object
   #   * `setSubscriptionHandle()` subscribes the the dataset for the current table state
@@ -479,19 +476,25 @@
 #   * these counts are published by the datatables publication

 DataTableSubscriptionCount = new Meteor.Collection "datatable_subscription_count"
+
+# ##### prepareCountCollection()
 Template.dataTable.prepareCountCollection = ->
   collection = @getData().countCollection or DataTableSubscriptionCount
   @setCountCollection collection

+# ##### getCollection()
 Template.dataTable.getCollection = ->
   return @getData().collection or false

+# ##### getCountCollection()
 Template.dataTable.getCountCollection = ->
   return @getData().countCollection or false

+# ##### getTotalCount()
 Template.dataTable.getTotalCount = ->
   return @getCountCollection().findOne( "#{ @getSubscription() }" ).count or false

+# ##### getFilteredCount()
 Template.dataTable.getFilteredCount = ->
   return @getCountCollection().findOne( "#{ @getSubscription() }_filtered" ).count or false

@@ -499,37 +502,46 @@
 #   * The reactive cursor responsible for keeping the client in sync
 #   * identical to the server cursor publishing the data, except it does not skip

+# ##### setCursor()
 Template.dataTable.setCursor = ( cursor ) ->
   Match.test cursor, Object
   @setData 'cursor', cursor
   @log "cursor:set", cursor

+# ##### prepareCursor()
 Template.dataTable.prepareCursor = ->
   return

+# ##### getCursor()
 Template.dataTable.getCursor = ->
   return @getData().cursor or false

 # ### DataTable Instance

+# ##### getDataTable()
 Template.dataTable.getDataTable = ->
   return @getTemplateInstance().dataTable or false

+# ##### setDataTable()
 Template.dataTable.setDataTable = ( dataTable ) ->
   Match.test dataTable, Object
   @getTemplateInstance().dataTable = dataTable
   @log "dataTable:set", dataTable.fnSettings()

+# ##### prepareDataTable()
 Template.dataTable.prepareDataTable = ->
   @setDataTable $(".#{ @getSelector() } table").dataTable( @getOptions() )

+# ##### prepareFilters()
 Template.dataTable.prepareFilters = ->
   @prepareFilterPlaceholder()
   @prepareFooterFilter()

+# ##### prepareFilterPlaceholder()
 Template.dataTable.prepareFilterPlaceholder = ->
   $(".#{ @getSelector() } .dataTables_filter input[type=text]").attr "placeholder", "Type to filter..."

+# ##### prepareFooterFilter()
 Template.dataTable.prepareFooterFilter = ->
   selector = @getSelector()
   if selector is 'datatable-add-row' and $.keyup
@@ -538,24 +550,30 @@
       target = @
       self.getDataTable().fnFilter target.value, $(".#{ self.getSelector() } .dataTables_wrapper tfoot input").index( target )

+# ##### preparePagination()
 Template.dataTable.preparePagination = ->
   unless $.select2
     $(".#{ @getSelector() } .dataTables_length select").select2 minimumResultsForSearch: "-1"

 # ## Utility Methods

+# ##### getTemplateInstance()
 Template.dataTable.getTemplateInstance = ->
   return @templateInstance or false

+# ##### getGuid()
 Template.dataTable.getGuid = ->
   return @guid or false

+# ##### getData()
 Template.dataTable.getData = ->
   return @getTemplateInstance().data or false

+# ##### setData()
 Template.dataTable.setData = ( key, data ) ->
   @templateInstance.data[ key ] = data

+# ##### arrayToDictionary()
 Template.dataTable.arrayToDictionary = ( array, key ) ->
   dict = {}
   dict[obj[key]] = obj for obj in array when obj[key]?
@@ -565,6 +583,7 @@

 # * TODO : this is temporary all of this should be passed in through the options param

+# ##### presetOptions()
 Template.dataTable.presetOptions = ->
   selector = @getSelector()


Activity: CommentList: Added number of replies check to hide/show replies link

--- servers/lib/crawler/staticpages/activity.coffee
+++ servers/lib/crawler/staticpages/activity.coffee
@@ -110,9 +110,7 @@

-
-          #{repliesText}
-
+        #{if count > 0 then "#{repliesText}" else ""}


Remove debugger

Remove debugger

--- drivers/javascript/net.coffee
+++ drivers/javascript/net.coffee
@@ -305,7 +305,6 @@
             while i < data.length
                 totalBuf.set(i+4, data.get(i))
                 i++
-            debugger

         @write totalBuf


try to make mouse spec more reliable, redux

--- spec/javascripts/jax/input/mouse_spec.js.coffee
+++ spec/javascripts/jax/input/mouse_spec.js.coffee
@@ -24,7 +24,7 @@
       target.style.top = '252px'
       document.body.appendChild target
       mouse = new Jax.Input.Mouse target
-      $(window).scrollTo 0, 0
+      window.scrollTo 0, 0
     afterEach ->
       document.body.removeChild target


Sort level search results in campaign editor better

--- app/views/editor/campaign/CampaignEditorView.coffee
+++ app/views/editor/campaign/CampaignEditorView.coffee
@@ -316,7 +316,16 @@
         LevelsNode.levels[level.get('original')] = level
         @settings.supermodel.registerModel level
       mapped = ({label: r.get('name'), value: r.get('original')} for r in collection.models)
-      res(mapped)
+
+      # Sort the results. Prioritize names that start with the search term, then contain the search term.
+      lowerPriority = _.clone(mapped)
+      lowerTerm = req.term.toLowerCase()
+      startsWithTerm = _.filter(lowerPriority, (item) -> _.string.startsWith(item.label.toLowerCase(), lowerTerm))
+      _.pull(lowerPriority, startsWithTerm...)
+      hasTerm = _.filter(lowerPriority, (item) -> _.string.contains(item.label.toLowerCase(), lowerTerm))
+      _.pull(lowerPriority, hasTerm...)
+      sorted = _.flatten([startsWithTerm, hasTerm, lowerPriority])
+      res(sorted)

 class LevelNode extends TreemaObjectNode
   valueClass: 'treema-level'


Translate course solutions better.

Translate course solutions better.

--- app/views/teachers/TeacherCourseSolutionView.coffee
+++ app/views/teachers/TeacherCourseSolutionView.coffee
@@ -33,20 +33,30 @@
       return '' if l isnt @language
       a

+  getText: (thang, what) ->
+    if thang.i18n
+      if i18n.lng() in Object.keys(thang.i18n)
+        return thang.i18n[i18n.lng()][what]
+    return thang[what]
+
   onLoaded: ->
+    @listenTo me, 'change:preferredLanguage', @refreshData
+    @refreshData()
+
+  refreshData: ->
     for level in @levels?.models
       articles = level.get('documentation')?.specificArticles
       if articles
         guide = articles.filter((x) => x.name == "Overview").pop()
-        level.set 'guide', marked(@hideWrongLanguage(guide.body)) if guide
+        level.set 'guide', marked(@hideWrongLanguage(@getText(guide, 'body'))) if guide
         intro = articles.filter((x) => x.name == "Intro").pop()
-        level.set 'intro', marked(@hideWrongLanguage(intro.body)) if intro
+        level.set 'intro', marked(@hideWrongLanguage(@getText(intro, 'body'))) if intro
       heroPlaceholder = level.get('thangs').filter((x) => x.id == 'Hero Placeholder').pop()
       comp = heroPlaceholder?.components.filter((x) => x.original.toString() == '524b7b5a7fc0f6d51900000e' ).pop()
       programmableMethod = comp?.config.programmableMethods.plan
       if programmableMethod
         try
-          translatedDefaultCode = _.template(programmableMethod.languages[level.get('primerLanguage') or @language] or programmableMethod.source)(programmableMethod.context)
+          translatedDefaultCode = _.template(programmableMethod.languages[level.get('primerLanguage') or @language] or programmableMethod.source)(@getText(programmableMethod, 'context'))
         catch e
           console.error('Broken solution for level:', level.get('name'))
           continue
@@ -56,7 +66,7 @@
         level.set 'begin', finalDefaultCode
         solution = _.find(programmableMethod.solutions, (x) => x.language is (level.get('primerLanguage') or @language))
         try
-          solutionText = _.template(solution?.source)(programmableMethod.context)
+          solutionText = _.template(solution?.source)(@getText(programmableMethod, 'context'))
         catch error
           solutionText = solution?.source
           console.error "Couldn't create solution template of", solution?.source, "\nwith context", programmableMethod.context, "\nError:", error


:bug: Ignore non-existing files

:bug: Ignore non-existing files

--- lib/open-last-project.coffee
+++ lib/open-last-project.coffee
@@ -1,5 +1,6 @@

+FS = require('fs')
 module.exports =
   Save:->
     Files = []
@@ -16,11 +17,17 @@
     LastProject = JSON.parse(LastProject)
     atom.project.setPaths [LastProject.Path]
     Promises = []
-    LastProject.Files.forEach (file)-> Promises.push atom.workspace.open(file)
+    LastProject.Files.forEach (file)->
+      Promises.push new Promise (resolve)->
+        FS.exists file, (Status)->
+          return resolve() unless Status
+          atom.workspace.open(file).then(resolve)
     Promise.all(Promises).then ->
       # Remove the empty pane
       atom.workspace.eachEditor (editor)->
         editor.destroy() unless editor.getPath()
       # Set the last active file
       return unless LastProject.CurrentFile
-      atom.workspace.open(LastProject.CurrentFile)
+      FS.exists LastProject.CurrentFile, (Status)->
+        return unless Status
+        atom.workspace.open LastProject.CurrentFile


Clicking within the text within the search input will now position the cursor, while clicking on the space after the search input will position the cursor at the end of the text.

--- coffee/chosen.jquery.coffee
+++ coffee/chosen.jquery.coffee
@@ -109,6 +109,7 @@
     @search_field.bind 'keyup.chosen', (evt) => this.keyup_checker(evt); return
     @search_field.bind 'keydown.chosen', (evt) => this.keydown_checker(evt); return
     @search_field.bind 'focus.chosen', (evt) => this.input_focus(evt); return
+    @search_field.bind 'click.chosen', (evt) => evt.stopPropagation(); return

     if @is_multiple
       @search_choices.bind 'click.chosen', (evt) => this.choices_click(evt); return
@@ -181,8 +182,8 @@
     @container.addClass "chosen-container-active"
     @active_field = true

+    @search_field.focus()
     @search_field.val(@search_field.val())
-    @search_field.focus()

   test_active_click: (evt) ->