(fix) deploy app on data init

(fix) deploy app on data init

--- src/steroids/servers/BuildServer.coffee
+++ src/steroids/servers/BuildServer.coffee
@@ -97,7 +97,7 @@
           res.header "Access-Control-Allow-Headers", "Content-Type"

           f(req, res).catch (err) ->
-            res.status(500).json {error: "Can not do anything lol"}
+            res.status(500).json {error: "Internal error"}

     @app.get "/", (req, res) =>
@@ -152,15 +152,17 @@
       res.header "Access-Control-Allow-Headers", "Content-Type"
       res.status(200).send "Pong!"

-    helper "get", "/__appgyver/deploy", (req, res) ->
+    @app.get "/__appgyver/deploy", (req, res) =>
+      res.header "Access-Control-Allow-Origin", "*"
+      res.header "Access-Control-Allow-Headers", "Content-Type"
+
       Deploy = require "../Deploy"
       deploy = new Deploy
-        sharePage: false

       deploy.run().then () ->
-        res.json deploy.cloudConfig
-      .catch Deploy.DeployError, (err) ->
-        res.status(500).json {error: "Can not deploy project"}
+        res.status(200).json deploy.cloudConfig
+      .catch Deploy.DeployError, (error) ->
+        res.status(500).json { error: error.message }

     @app.get "/__appgyver/app_config", (req, res) =>
       res.header "Access-Control-Allow-Origin", "*"
@@ -170,7 +172,8 @@
         appConfig = require Paths.application.configs.app
         res.status(200).json appConfig
       else
-        res.status(204).send ''
+        error = "Could not find #{Paths.appConfig.configs.app}"
+        res.status(404).json { error: error }

     @app.get "/__appgyver/cloud_config", (req, res) =>
       res.header "Access-Control-Allow-Origin", "*"
@@ -178,11 +181,11 @@

       if fs.existsSync Paths.application.configs.cloud
         cloudConfig = require Paths.application.configs.cloud
-        res.json cloudConfig
+        res.status(200).json cloudConfig
       else
         error = "Could not find config/cloud.json. Please run $ steroids deploy."

-        res.status(404).json {error: error}
+        res.status(404).json { error: error }

     helper "get", "/__appgyver/data/config", (req, res) =>
       data = new Data
@@ -193,6 +196,8 @@
       data = new Data
       data.init().then ->
         res.status(200).send "Success!"
+      .catch (error) ->
+        res.status(500).json { error: error.message }

     helper "post", "/__appgyver/data/sync", (req, res) =>
       data = new Data


fixes #1100: precedence in or-test compilation of `in`

--- nodes.coffee
+++ nodes.coffee
@@ -1376,7 +1376,7 @@
     [sub, ref] = @object.cache o, LEVEL_OP
     [cmp, cnj] = if @negated then [' !== ', ' && '] else [' === ', ' || ']
     tests = for item, i in @array.base.objects
-      (if i then ref else sub) + cmp + item.compile o, LEVEL_OP
+      (if i then ref else sub) + cmp + item.compile o, LEVEL_ACCESS
     return 'false' if tests.length is 0
     tests = tests.join cnj
     if o.level < LEVEL_OP then tests else "(#{tests})"


fixed a bug where `while` condition was compiling as statement

--- nodes.coffee
+++ nodes.coffee
@@ -1126,9 +1126,9 @@
   compileNode: (o) ->
     top      =  del(o, 'top') and not @returns
     o.indent =  @idt 1
-    o.top    =  true
     @condition.parenthetical = yes
     cond     =  @condition.compile(o)
+    o.top    =  true
     set      =  ''
     unless top
       rvar  = o.scope.freeVariable 'result'


Code cleanup in Class

Code cleanup in Class

* Don't insert unnecessary `_ref` (in the wrong scope)
* Improve code prettiness. (Reverts most of 903e9c99)

--- nodes.coffee
+++ nodes.coffee
@@ -1061,40 +1061,32 @@

   # Make sure that a constructor is defined for the class, and properly
   # configured.
-  ensureConstructor: (name, o) ->
-    missing = not @ctor
-    @ctor or= new Code
+  ensureConstructor: (name) ->
+    if not @ctor
+      @ctor = new Code
+      if @externalCtor
+        @ctor.body.push new Literal "#{@externalCtor}.apply(this, arguments)"
+      else if @parent
+        @ctor.body.push new Literal "#{name}.__super__.constructor.apply(this, arguments)"
+      @ctor.body.makeReturn()
+      @body.expressions.unshift @ctor
     @ctor.ctor = @ctor.name = name
     @ctor.klass = null
     @ctor.noReturn = yes
-    if missing
-      superCall = new Literal "#{name}.__super__.constructor.apply(this, arguments)" if @parent
-      superCall = new Literal "#{@externalCtor}.apply(this, arguments)" if @externalCtor
-      if superCall
-        ref = new Literal o.scope.freeVariable 'ref'
-        @ctor.body.unshift new Assign ref, superCall
-      @addBoundFunctions o
-      if superCall
-        @ctor.body.push ref
-        @ctor.body.makeReturn()
-      @body.expressions.unshift @ctor
-    else
-      @addBoundFunctions o
-

   # Instead of generating the JavaScript string directly, we build up the
   # equivalent syntax tree and compile that, in pieces. You can see the
   # constructor, property assignments, and inheritance getting built out below.
   compileNode: (o) ->
-    decl  = @determineName()
-    name  = decl or '_Class'
+    name  = @determineName() or '_Class'
     name = "_#{name}" if name.reserved
     lname = new Literal name

     @hoistDirectivePrologue()
     @setContext name
     @walkBody name, o
-    @ensureConstructor name, o
+    @ensureConstructor name
+    @addBoundFunctions o
     @body.spaced = yes
     @body.expressions.unshift @ctor unless @ctor instanceof Code
     @body.expressions.push lname


dynakeys: work again on JScript

dynakeys: work again on JScript

--- nodes.coffee
+++ nodes.coffee
@@ -579,13 +579,13 @@
     return (if @front then '({})' else '{}') unless props.length
     for prop, i in props
       if prop instanceof Splat or (prop.variable or prop).base instanceof Parens
-        rest = @properties.splice i
+        rest = props.splice i, 1/0
         break
     idt         = o.indent += TAB
     nonComments = (prop for prop in @properties when prop not instanceof Comment)
     lastNoncom  = last nonComments
-    props = for prop, i in @properties
-      join = if i is @properties.length - 1
+    props = for prop, i in props
+      join = if i is props.length - 1
         ''
       else if prop is lastNoncom or prop instanceof Comment
         '\n'


Fixing issue #678 -- missing parentheses in a mixed operation(call(soak))

--- nodes.coffee
+++ nodes.coffee
@@ -440,22 +440,23 @@
   # Compile a vanilla function call.
   compileNode: (o) ->
     o.chainRoot = this unless o.chainRoot
+    op = @tags.operation
     if @exist
       [@first, @meth] = @variable.compileReference o, precompile: yes
       @first = "(typeof #{@first} === \"function\" ? "
       @last  = " : undefined)"
     else if @variable then @meth = @variable.compile o
     for arg in @args when arg instanceof SplatNode
-      compilation = @compileSplat(o)
-    if not compilation
+      code = @compileSplat(o)
+    if not code
       args = for arg in @args
         arg.parenthetical = true
         arg.compile o
-      compilation = if @isSuper
+      code = if @isSuper
         @compileSuper(args.join(', '), o)
       else
         "#{@first}#{@prefix()}#{@meth}(#{ args.join(', ') })#{@last}"
-    compilation
+    if op and @variable and @variable.wrapped then "(#{code})" else code

   # `super()` is converted into a call against the superclass's implementation
   # of the current function.


drying up compileSplice

drying up compileSplice

--- nodes.coffee
+++ nodes.coffee
@@ -966,21 +966,18 @@
   # Compile the assignment from an array splice literal, using JavaScript's
   # `Array#splice` method.
   compileSplice: (o) ->
-    {range} = @variable.properties.pop()
-    name    = @variable.compile o
-    excl    = range.exclusive
-    to      = "#{name}.length" unless range.to
-    if range.from
-      [fromDecl, fromRef] = range.from.cache o, LEVEL_OP
-    else
-      [fromDecl, fromRef] = ['0', '0']
-    unless to
-      if range.from and range.from.isSimpleNumber() and range.to.isSimpleNumber()
-        to = +range.to.compile(o) - +fromRef
-        to += 1 unless excl
+    {range: {from, to, exclusive}} = @variable.properties.pop()
+    name = @variable.compile o
+    [fromDecl, fromRef] = if from then from.cache(o, LEVEL_OP) else ['0', '0']
+    if to
+      if from and from.isSimpleNumber() and to.isSimpleNumber()
+        to = +to.compile(o) - +fromRef
+        to += 1 unless exclusive
       else
-        to = range.to.compile(o) + ' - ' + fromRef
-        to += ' + 1' unless excl
+        to = to.compile(o) + ' - ' + fromRef
+        to += ' + 1' unless exclusive
+    else
+      to = "#{name}.length"
     val = @value.compile(o)
     "[].splice.apply(#{name}, [#{fromDecl}, #{to}].concat(#{val}))"


Show lenny if no errors exist.

Show lenny if no errors exist.

--- lib/views/analysis_view.coffee
+++ lib/views/analysis_view.coffee
@@ -46,8 +46,11 @@
     return false

   render: ->
-    unless @props.items
-      return div className: 'lenny', "( ͡° ͜ʖ ͡°) doesn't see any problems. Relax, man."
+    if @props.items.length == 0
+      return div className: 'tool-panel panel-bottom padded',
+        div className: 'pull-right',
+          a href: '#', className: 'icon icon-x', rel: 'dismiss', onClick: @dismiss
+        div className: 'lenny', "( ͡° ͜ʖ ͡°) doesn't see any problems. Relax, man."

     div className: 'tool-panel panel-bottom padded',
       div className: 'pull-right',


fix support of 'StopCountdown'

fix support of 'StopCountdown'

--- src/webapp/_scripts/r7/Rules4Countdown.coffee
+++ src/webapp/_scripts/r7/Rules4Countdown.coffee
@@ -23,6 +23,9 @@
         when "StartCountdown"
           _tasks[e.key] = e
           _states.update(out, e.key, e.timeout, onUpdateState)
+        when "StopCountdown"
+          delete _tasks[e.key]
+          delete _states[e.key]
         when "Tick"
           decCountdown(out, e.delta500 / 2)  if e.delta500 > 0
         else


:lipstick:

:lipstick:

--- src/app/editor.coffee
+++ src/app/editor.coffee
@@ -880,8 +880,8 @@
       for token in screenLine.tokens
         updateScopeStack(token.scopes)
         line.push(token.getValueAsHtml(
-          showInvisibles: @showInvisibles,
-          invisiblesMap: @rootView()?.getInvisiblesMap(),
+          showInvisibles: @showInvisibles
+          invisiblesMap: @rootView()?.getInvisiblesMap()
           hasLeadingWhitespace: position < firstNonWhitespacePosition
           hasTrailingWhitespace: position + token.value.length > firstTrailingWhitespacePosition
         ))


Simplify handling of 'mousewheel' event

Simplify handling of 'mousewheel' event

--- src/app/editor.coffee
+++ src/app/editor.coffee
@@ -337,11 +337,8 @@
       false

     @scrollView.on 'mousewheel', (e) =>
-      e = e.originalEvent
-      if e.wheelDeltaY
-        newEvent = document.createEvent("WheelEvent");
-        newEvent.initWebKitWheelEvent(0, e.wheelDeltaY, e.view, e.screenX, e.screenY, e.clientX, e.clientY, e.ctrlKey, e.altKey, e.shiftKey, e.metaKey)
-        @verticalScrollbar.get(0).dispatchEvent(newEvent)
+      if delta = e.originalEvent.wheelDeltaY
+        @scrollTop(@scrollTop() - delta)
         false

     @verticalScrollbar.on 'scroll', =>


Use `atom.confirm` to display the confirmation dialog when reloading

--- src/app/window.coffee
+++ src/app/window.coffee
@@ -73,14 +73,12 @@

   reload: ->
     if rootView.getModifiedBuffers().length > 0
-      message = "There are unsaved buffers, reload anyway?"
-      detailedMessage = "You will lose all unsaved changes if you reload"
-      buttons = [
-        ["Reload", -> Native.reload()]
-        ["Cancel", ->]
-      ]
-
-      Native.alert(message, detailedMessage, buttons)
+      atom.confirm(
+        "There are unsaved buffers, reload anyway?",
+        "You will lose all unsaved changes if you reload",
+        "Reload", (-> Native.reload()),
+        "Cancel"
+      )
     else
       Native.reload()


add a socketing event

add a socketing event

--- library/membrane/duplex.coffee
+++ library/membrane/duplex.coffee
@@ -248,6 +248,7 @@
         try @socket = io.connect @duplex, options catch error
             message = "blew up Socket.IO: #{error.message}"
             error.message = message.toString(); throw error
+        @emit "socketing", this.socket, this.duplex, options
         failed = "failed to establish the Socket.IO connection"
         assert _.isFunction(@socket.emit), failed; @feedback()
         osc = (listener) => @socket.on "connect", listener


don't let the providers be called when conn is lost

--- library/membrane/duplex.coffee
+++ library/membrane/duplex.coffee
@@ -339,6 +339,8 @@
     consumeProviders: external (socket) ->
         assert _.isFunction o = -> try _.head arguments
         assert _.isFunction i = -> try _.head arguments
+        assert srv = try this.service.toString() or null
+        notConnected = "service #{srv} is not connected"
         for provider in @providers then do (provider) =>
             message = "Provider %s at %s using nsp=%s"
             assert _.isString uloc = @location.underline
@@ -346,6 +348,7 @@
             logger.info message, provider.bold, uloc, unsp
             this.emit "install-provider", socket, provider
             this[provider] = (parameters..., callback) ->
+                assert @booted and @duplexed, notConnected
                 callback = (->) unless _.isFunction callback
                 noCallback = "#{callback} is not a callback"
                 assert _.isFunction(callback), noCallback


minor reorders in the visual core

minor reorders in the visual core

--- library/membrane/visual.coffee
+++ library/membrane/visual.coffee
@@ -264,7 +264,7 @@
         assert _.isFunction(@prelude), noPrelude
         assert _.isObject context = stock or {}
         @energizeContext.call @, context, symbol
-        run = (ff) -> fn symbol, context, request
+        run = (fn) -> fn symbol, context, request
         run prelude = this.downstream prelude: =>
             context.snapshot = try _.keys context
             assert @deployContext context, symbol


minor changes in the composition sys

minor changes in the composition sys

--- library/nucleus/compose.coffee
+++ library/nucleus/compose.coffee
@@ -75,7 +75,9 @@
             assert _.isArray a = accumulate = new Array
             subject = this unless _.isObject try subject
             classed = _.isObject subject.__super__ or null
-            assert classed, "supplied object is not a class"
+            assert idc = try subject.identify() or undefined
+            assert idc = try subject.toString() unless idc
+            assert classed, "object is not a class: #{idc}"
             scanner = (fn) -> fn a while subject?; return a
             return scanner _.identity (accumulate) -> try
                 subject = subject.__super__ or undefined


Make the package description more clear

Make the package description more clear

--- package.cson
+++ package.cson
@@ -1,7 +1,7 @@
 'name': 'vim-mode'
 'main': './lib/vim-mode'
 'version': '0.0.1'
-'description': 'Provides vim modal control of Atom.'
+'description': 'Provides vim modal control of Atom, currently INCOMPLETE.'
 'repository':
   'type': 'git'
   'url': 'https://github.com/atom/vim-mode.git'


add path view helper à la @joshrowley

add path view helper à la @joshrowley

--- lib/compiler.coffee
+++ lib/compiler.coffee
@@ -65,7 +65,11 @@
   constructor: (@roots, @extensions, @compile_options, @category, @file) ->
     @adapters = get_adapters.call(@)
     @is_compiled = !!_(@adapters).pluck('name').compact().value().length
-    @file_options = filename: @file.path
+    @file_options =
+      filename: @file.path
+      path: @roots.config.out(
+        new File(base: @roots.root, path: @file.path), _.last(@adapters).output
+      )

   ###*
    * Initialize the actual compilation. This method is a higher level wrapper


fix docs for jenkins

fix docs for jenkins

--- src/scripts/jenkins.coffee
+++ src/scripts/jenkins.coffee
@@ -1,5 +1,5 @@
 # Interact with your jenkins CI server, assumes you have a parameterized build
-# with the parameter
+# with the branch to build as a parameter
 #
 # You need to set the following variables:
 #   HUBOT_JENKINS_URL = "http://ci.example.com:8080"


Update extended.coffee

Update extended.coffee

--- lib/extended.coffee
+++ lib/extended.coffee
@@ -1,5 +1,9 @@
 _ = require "underscore"

+
+# extend nedb to give us a Schema we can generate easily with a stale timestamp
+# this will allow us to do a very simpe garbage collection for these `temporary`
+# objects
 extended = (ds) ->

   _.extend @, ds
@@ -13,6 +17,9 @@

   @

+
+# Schema takes opts, and you can really extend that to as large as you'd like
+# I'll play with making this more accessible to everyone.
 extended::Schema = (opts) ->

   stale = 1000 * 60 * 60
@@ -33,8 +40,6 @@
       garbageCollection self.stale
     , self.stale

-  else garbageCollection self.stale
-
   @stale = Date.now() + self.stale

   @


Add helper method to goto a course by name

--- test-integration/helpers/course-select.coffee
+++ test-integration/helpers/course-select.coffee
@@ -35,4 +35,7 @@
       when 'CONCEPT_COACH' then @el.courseLink.get(null, true).click()
       else @el.courseLink.get().click()

+  goToCourseByName: (name) ->
+    test.waitClick(css: "[data-title='#{name}'] > a")
+
 module.exports = CourseSelect


Specify config with JSON schema instead of obsolete `configDefaults`

--- lib/auto-update-packages.coffee
+++ lib/auto-update-packages.coffee
@@ -6,16 +6,16 @@
   fs ?= require 'fs-plus'

 NAMESPACE = 'auto-update-packages'
-CONFIG_KEY_INTERVAL_MINUTES = 'intervalMinutes'
-
-CONFIG_DEFAULTS = {}
-CONFIG_DEFAULTS[CONFIG_KEY_INTERVAL_MINUTES] = 6 * 60
-
 WARMUP_WAIT = 10 * 1000
 MINIMUM_AUTO_UPDATE_BLOCK_DURATION_MINUTES = 15

 module.exports =
-  configDefaults: CONFIG_DEFAULTS
+  config:
+    intervalMinutes:
+      type: 'integer'
+      minimum: MINIMUM_AUTO_UPDATE_BLOCK_DURATION_MINUTES
+      default: 6 * 60
+      title: 'Auto-Update Interval Minutes'

   activate: (state) ->
     atom.workspaceView.command "#{NAMESPACE}:update-now", =>
@@ -58,7 +58,7 @@
     @saveLastUpdateTime()

   getAutoUpdateBlockDuration: ->
-    minutes = atom.config.get([NAMESPACE, CONFIG_KEY_INTERVAL_MINUTES].join('.'))
+    minutes = atom.config.get("#{NAMESPACE}.intervalMinutes")

     if minutes < MINIMUM_AUTO_UPDATE_BLOCK_DURATION_MINUTES
       minutes = MINIMUM_AUTO_UPDATE_BLOCK_DURATION_MINUTES


Allow Auto Activation If Bracket Matcher Triggers

--- lib/autocomplete-manager.coffee
+++ lib/autocomplete-manager.coffee
@@ -363,7 +363,7 @@
     return if @disposed
     return @hideSuggestionList() if @compositionInProgress
     autoActivationEnabled = atom.config.get('autocomplete-plus.enableAutoActivation')
-    wouldAutoActivate = newText.trim().length is 1 or ((@backspaceTriggersAutocomplete or @suggestionList.isActive()) and oldText.trim().length is 1)
+    wouldAutoActivate = newText.trim().length in [1..2] or ((@backspaceTriggersAutocomplete or @suggestionList.isActive()) and oldText.trim().length in [1..2])

     if autoActivationEnabled and wouldAutoActivate
       @cancelHideSuggestionListRequest()


:art: Clarify How Activation Occurs

:art: Clarify How Activation Occurs

--- lib/autocomplete-manager.coffee
+++ lib/autocomplete-manager.coffee
@@ -357,9 +357,15 @@
     return if @disposed
     return @hideSuggestionList() if @compositionInProgress
     autoActivationEnabled = atom.config.get('autocomplete-plus.enableAutoActivation')
-    # TODO: Can we avoid bracket-matcher causing a 2-character change in the buffer changed event (can we get each character separately)?
-    wouldAutoActivate = (newText is ' ' or newText.trim().length is 1 or newText in @bracketMatcherPairs) or ((@backspaceTriggersAutocomplete or @suggestionList.isActive()) and (oldText is ' ' or oldText.trim().length is 1 or oldText in @bracketMatcherPairs))
-
+    wouldAutoActivate = false
+    if newText?.length
+      # Activate on space, a non-whitespace character, or a bracket-matcher pair
+      wouldAutoActivate = newText is ' ' or newText.trim().length is 1 or newText in @bracketMatcherPairs
+    else if oldText?.length
+      # Suggestion list must be either active or backspaceTriggersAutocomplete must be true for activation to occur
+      # Activate on removal of a space, a non-whitespace character, or a bracket-matcher pair
+      wouldAutoActivate = (@backspaceTriggersAutocomplete or @suggestionList.isActive()) and (oldText is ' ' or oldText.trim().length is 1 or oldText in @bracketMatcherPairs)
+
     if autoActivationEnabled and wouldAutoActivate
       @cancelHideSuggestionListRequest()
       @requestNewSuggestions()


update dashboard spec with latest from dashboard json update

--- test/components/student-dashboard.spec.coffee
+++ test/components/student-dashboard.spec.coffee
@@ -73,7 +73,7 @@
       feedback = state.div.querySelectorAll('.-upcoming .task .feedback span')
       expect(_.pluck(feedback, 'textContent'))
         .to.have.deep.equal([
-          '6/7 correct', '7/8 correct', '6/6 answered', '7/3 answered', 'Not started', 'Not started'
+          '6/7 correct', '7/8 correct', '6/6 answered', '7/3 answered', '0/2 answered', 'Not started', 'Not started'
         ])

   it 'renders events to week panel', ->
@@ -86,6 +86,7 @@
       expect(_.pluck(tasks, 'textContent')).to.have.deep.equal([
         'Homework #3',
         'Homework #4 (final)',
+        'Chapter 6 Homework Before Due, no feedback',
         'Chapter 5 and Chapter 6 Reading',
         'Chapter 10 and Chapter 11 Reading, multi-part'
       ])
@@ -97,7 +98,7 @@
         _.without(el.classList, 'task', 'row', 'homework', 'reading')
       )
       expect(classes)
-        .to.have.deep.equal([['workable'], ['workable'], [], [], [], []])
+        .to.have.deep.equal([['workable'], ['workable'], [], [], [], [], []])

   it 'displays redirect when a CC course', ->
     @course = _.clone(STUDENT_COURSE_ONE_MODEL)


Tests: Code clean.

Tests: Code clean.

--- tests/src/activity/activity.coffee
+++ tests/src/activity/activity.coffee
@@ -50,9 +50,9 @@
       .setValue                   selector + ' .edit-widget [testpath=ActivityInputView]', post + '\n'
       .pause                      3000

-    browser.assert.containsText selector, post # Assertion
-
-    browser.end()
+    browser
+      .assert.containsText selector, post # Assertion
+      .end()

   deletePost: (browser) ->


:bug: editor wasn't set (oversight on refactoring)

--- lib/cursor-style-manager.coffee
+++ lib/cursor-style-manager.coffee
@@ -27,7 +27,8 @@

     when 'linewise'
       bufferPoint = swrap(selection).getBufferPositionFor('head', fromProperty: true)
-      if cursor.editor.isSoftWrapped()
+      editor = cursor.editor
+      if editor.isSoftWrapped()
         screenPoint = editor.screenPositionForBufferPosition(bufferPoint)
         offset = screenPoint.traversalFrom(cursor.getScreenPosition())
       else


Add issue URL link for selector deprecations

Closes #23

--- lib/deprecation-cop-view.coffee
+++ lib/deprecation-cop-view.coffee
@@ -51,7 +51,7 @@
     @on 'click', '.deprecation-info', ->
       $(this).parent().toggleClass('collapsed')

-    @on 'click', '.stack-line-location, .source-file a', ->
+    @on 'click', '.stack-line-location, .source-url', ->
       pathToOpen = @href.replace('file://', '')
       pathToOpen = pathToOpen.replace(/^\//, '') if process.platform is 'win32'
       atom.open(pathsToOpen: [pathToOpen])
@@ -135,6 +135,15 @@
     title = "#{deprecation.getOriginName()} is deprecated."
     stacktrace = stack.map(({functionName, location}) -> "#{functionName} (#{location})").join("\n")
     body = "#{deprecation.getMessage()}\n```\n#{stacktrace}\n```"
+    "#{repoUrl}/issues/new?title=#{encodeURI(title)}&body=#{encodeURI(body)}"
+
+  createSelectorIssueUrl: (packageName, deprecation, sourcePath) ->
+    return unless repo = atom.packages.getActivePackage(packageName)?.metadata?.repository
+    repoUrl = repo.url ? repo
+    repoUrl = repoUrl.replace(/\.git$/, '')
+
+    title = deprecation.message
+    body = "In #{sourcePath}: #{deprecation.message}"
     "#{repoUrl}/issues/new?title=#{encodeURI(title)}&body=#{encodeURI(body)}"

   updateCalls: ->
@@ -189,6 +198,7 @@
   updateSelectors: ->
     @refreshSelectorsButton.hide()
     @selectorList.empty()
+    self = this

     for packageName, deprecationsByFile of getSelectorDeprecations()
       @selectorList.append $$ ->
@@ -199,7 +209,13 @@
           @ul class: 'list', =>
             for sourcePath, deprecations of deprecationsByFile
               @li class: 'list-item source-file', =>
-                @a href: path.join(deprecations[0].packagePath, sourcePath), sourcePath
+                @a class: 'source-url', href: path.join(deprecations[0].packagePath, sourcePath), sourcePath
                 @ul class: 'list', =>
                   for deprecation in deprecations
-                    @li class: 'list-item text-success', deprecation.message
+                    @li class: 'list-item', =>
+                      @span class: 'text-warning icon icon-alert'
+                      @span class: 'list-item deprecation-message', deprecation.message
+
+                      @div class: 'btn-toolbar', =>
+                        if url = self.createSelectorIssueUrl(packageName, deprecation, sourcePath)
+                          @a class: 'issue-url', href: url, "Create Issue on #{packageName} repo"


Modified to use 'pkg' vs. 'package' for var name (reserved word package on line 21 error)

--- lib/info/npm.coffee
+++ lib/info/npm.coffee
@@ -18,7 +18,7 @@
         for line in lines
           packageName = line.match /^(├──|├─┬|└─┬|└──) (.+)$/
           if packageName and packageName[2]
-            package = packageName[2].split '@'
-            packages.push {label: package[0], version: package[1], value: 'http://search.npmjs.org/#/' + package[0]}
+            pkg = packageName[2].split '@'
+            packages.push {label: pkg[0], version: pkg[1], value: 'http://search.npmjs.org/#/' + pkg[0]}
         callback packages


bump version to 1.0.2

bump version to 1.0.2

--- gulp/config.coffee
+++ gulp/config.coffee
@@ -8,7 +8,7 @@
   constructor: ->
     @_GLOBALS_DEFAULTS = {
       defaults: {
-        BUNDLE_VERSION: "1.0.1"
+        BUNDLE_VERSION: "1.0.2"

         AVAILABLE_PLATFORMS: ["ios", "android"]


use timeout in scheduling and always schedule

never call updateDiffs() directly

--- lib/git-diff-staged-view.coffee
+++ lib/git-diff-staged-view.coffee
@@ -7,8 +7,8 @@
     @reset()
     @editor = editor

-    @subscriptions.add(@editor.onDidStopChanging(@updateDiffs))
-    @subscriptions.add(@editor.onDidChangePath(@updateDiffs))
+    @subscriptions.add @editor.onDidChange(@scheduleUpdate)
+    @subscriptions.add @editor.onDidChangePath(@scheduleUpdate)

     @subscribeToRepository()
     @subscriptions.add atom.project.onDidChangePaths @subscribeToRepository
@@ -18,13 +18,13 @@
     editorElement = atom.views.getView(editor)
     @subscriptions.add atom.commands.add editorElement, 'git-diff-staged:update-diffs', @scheduleUpdate

-    @scheduleUpdate()
+    @scheduleUpdate(100)

   reset: ->
     @subscriptions = new CompositeDisposable()
     @decorations = {}
     @markers = []
-    @immediateId = @repository = @editor = null
+    @timeoutId = @repository = @editor = null

   dispose: =>
     @cancelUpdate()
@@ -39,11 +39,11 @@
         @scheduleUpdate() if changedPath is @editor.getPath()

   cancelUpdate: ->
-    clearImmediate(@immediateId)
+    clearTimeout(@timeoutId)

-  scheduleUpdate: =>
+  scheduleUpdate: (timeout = 50)=>
     @cancelUpdate()
-    @immediateId = setImmediate(@updateDiffs)
+    @timeoutId = setTimeout(@updateDiffs, timeout)

   updateDiffs: =>
     return if @editor.isDestroyed()


make require late in r_misc so static react still works

--- src/smc-webapp/r_misc.cjsx
+++ src/smc-webapp/r_misc.cjsx
@@ -20,7 +20,6 @@

 async = require('async')

-{salvus_client} = require('./salvus_client')

 {React, ReactDOM, rclass, rtypes, is_redux, is_redux_actions} = require('./smc-react')
 {Alert, Button, ButtonToolbar, Col, Input, OverlayTrigger, Popover, Tooltip, Row, Well} = require('react-bootstrap')
@@ -980,6 +979,7 @@
 exports.course_warning = (pay) ->
     if not pay
         return false
+    {salvus_client} = require('./salvus_client')
     return salvus_client.server_time() <= misc.months_before(-3, pay)  # require subscription until 3 months after start (an estimate for when class ended, and less than when what student did pay for will have expired).

 project_warning_opts = (opts) ->
@@ -1009,6 +1009,7 @@
     else
         action = <billing.BillingPageLink text="buy a course subscription" />
     is_student = account_id == course_info.get('account_id') or email_address == course_info.get('email_address')
+    {salvus_client} = require('./salvus_client')
     if pay > salvus_client.server_time()  # in the future
         if is_student
             deadline  = Your instructor requires you to {action} within <TimeAgo date={pay}/>.


Fix #328.

Fix #328.

--- lib/locparse.coffee
+++ lib/locparse.coffee
@@ -47,7 +47,7 @@
 	if line.match /^\s+$/
 		log.warn lineNumber, 'W4', 'line of spaces' # warn 4
 		return true
-
+
 	return line is ''

 checkSpaces = (line, lineNumber, log) ->
@@ -70,12 +70,12 @@
 postCheck = (log) ->
 	log.setFilename 'post processing'
 	res = log.result
-
+
 	checkPropUniqueness res.areas, 'areas', 'N/a', 'id', log # error N
 	checkPropUniqueness res.locations, 'locations', 'N/a', 'id', log # error N
 	checkPropUniqueness res.locations, 'locations', 'E7', 'label', log # error 7
 	log.error 'locations', 'E6', "default was not found" unless res.defaultLocation? # error 6
-
+
 	labels = {}
 	labels[loc.label] = loc for loc in res.locations
 	for loc in res.locations
@@ -102,18 +102,18 @@
 class Logger
 	constructor: (@result, @verbose) ->
 		@filename = undefined
-
+
 	_add: (toWhere, what) ->
 		toWhere.push what
 		if @filename of @result.files
 			@result.files[@filename].push what
 		else
 			@result.files[@filename] = [what]
-
+
 	setFilename: (filename) ->
 		@filename = filename
 		console.log " --- #{filename}:" if @verbose
-
+
 	warn: (pointer, id, message) ->
 		pointer = "line #{pointer+1}" if typeof pointer == 'number'
 		console.warn "Warning(#{id}): #{pointer}: #{message}" if @verbose
@@ -124,7 +124,7 @@
 			pointer: pointer
 			message: message
 		)
-
+
 	error: (pointer, id, message) ->
 		pointer = "line #{pointer+1}" if typeof pointer == 'number'
 		console.warn "Error(#{id}): #{pointer}: #{message}" if @verbose
@@ -140,7 +140,7 @@
 class Result
 	verbose = false
 	filename = undefined
-
+
 	constructor: () ->
 		@areas = []
 		@locations = []
@@ -148,20 +148,20 @@
 		@errors = []
 		@warnings = []
 		@files = {}
-
+
 	save: (dbConnection) ->
 		throw new Error("Can't save with errors.") if @errors.length > 0
-
+
 		for area in @areas
 			dbConnection.query.sync(
 				dbConnection
 				"REPLACE areas (id, title, description) VALUES(?, ?, ?)"
-				[area.id, area.description, area.title]
+				[area.id, area.name, area.description]
 			)
-
+
 		locByLabel = {}
 		locByLabel[loc.label] = loc for loc in @locations
-
+
 		for loc in @locations
 			goto = ("#{v}=#{locByLabel[k].id}" for k,v of loc.actions)
 			dbConnection.query.sync(
@@ -180,107 +180,107 @@
 	blankLines = 0
 	for line, i in lines
 		checkSpaces line, i, log
-
+
 		if isEmpty(line, i, log)
 			blankLines++
 			continue
-
+
 		if isAreaLabel(line, i, log)
 			if area?
 				log.error i, 'N/a', "area has been already defined" # error N
-
+
 			if blankLines < i
 				log.warn i, 'W7', "#{i-blankLines} skipped non-empty line(s) before area" # warn 7
-
+
 			localAreaName = line.substr(2)
 			if areaName != localAreaName
 				log.error i, 'E5', "names <#{areaName}>(folder) and <#{localAreaName}>(file) don't match" # error 5
-
+
 			area = new Area(areaName, areaLabel)
 			log.result.areas.push(area)
 			location = null
-
+
 		else if not area?
 			continue # so we don't drop blankLines counter (for non-empty lines count, it's warn 7)
-
+
 		else if isLocationLabel(line, i, log)
 			[name, label, prop] = line.substr(4).split(/\s*`\s*/)
-
+
 			unless label?
 				log.error i, 'E3', "location should have `label` after name" # error 3
 				label = ''
 				prop = ''
-
+
 			prop = prop.trim()
-
+
 			label = area.label + '/' + label unless '/' in label
-
+
 			location = new Location(name, label, area)
-
+
 			if prop is '(default)'
 				log.error i, 'E11', "second default location found" if log.result.defaultLocation? # error 11
 				log.result.defaultLocation = location
 			else if prop isnt ''
 				log.warn i, 'N/a', "text after location label will be ignored (#{prop})" # warn N
-
+
 			area.locations.push(location)
 			log.result.locations.push(location)
-
+
 		else if isListItem(line, i, log)
 			unless location?
 				log.error i, 'N/a', "actions are only avaliable for locations" # error N
 				continue
-
+
 			[name, target, rem] = line.substr(2).split(/\s*`\s*/)
-
+
 			unless target?
 				log.error i, 'E2', "action should have `label` after name" # error 2
 				target = ''
-
+
 			log.warn i, 'W8', "Unnecessary trailing dot" if name[name.length-1] is '.' # warn 8
-
+
 			log.warn i, 'N/a', "text after target label will be ignored (#{rem})" if rem is not '' # warn N
-
+
 			target = location.area.label + '/' + target unless '/' in target
-
+
 			log.warn i, 'W9', "action `#{target}` has been doubled" if target of location.actions # warn 9
-
+
 			location.actions[target] = name
-
+
 		else if imageDescr=line.match /!\[(.+)\]\((.+)\)/
 			unless location?
 				log.error i, 'N/a', "images are only avaliable for locations" # error N
 				continue
-
+
 			log.error i, 'E9', "location's image has been doubled" if location.picture? # error 9
-
+
 			[_, path, path2] = imageDescr
 			if path isnt path2
 				log.error i, 'E10', "image paths are not equal: '#{path}', '#{path2}'" # error 10
-
+
 			location.picture = path
-
+
 		else
 			curObj = location || area
 			curObj.description += '\n' if blankLines >= 1 and curObj.description isnt ''
 			curObj.description += line
-
+
 		blankLines = 0

 processDir = (dir, parentLabel, log) ->
 	log.setFilename dir
-
+
 	unless t = dir.match /\/([^\/]+)\s-\s([^\/]+)\/?$/
 		log.error 'dirname', 'E4', "wrong path <#{dir}>, folder must have name like 'Area name - label'" # error 4
 		t = [null, dir, 'error'] # пусть хоть как-то дальше парсит, м.б. ещё какие ошибки нйдёт
-
+
 	[_, name, label] = t
 	label = parentLabel + '-' + label unless parentLabel is ''
-
+
 	checkSpaces name, 'name in folder name'
 	checkSpaces label, 'label in folder name'
-
+
 	files = fs.readdirSync(dir)
 	for filename in files
 		continue if filename[0] is '.' # what is hidden should be hidden


fix config issue

fix config issue

--- src/jquery.language.coffee
+++ src/jquery.language.coffee
@@ -8,10 +8,10 @@
     urlParam: 'language'
     cookieName: 'language'

-  constructor: (options) ->
+  constructor: (@options) ->
     @_activeLanguage = null
     @_initialized    = false
-    @config jQuery.extend(options, @options)
+    @config jQuery.extend(@options, @constructor.options)

   config: (options) =>
     return @options if !options && @_initialized


Fix messages spanning on multiple lines

Fix messages spanning on multiple lines

--- src/js/sampleSpec_2.coffee
+++ src/js/sampleSpec_2.coffee
@@ -815,21 +815,21 @@
 battery -> floor ; give 1 battery

 snow -> snow-trampled
-snow-rad -> snow-rad-trampled ; hurt 1 ; message "This heavily\nradiated snow hurts"
+snow-rad -> snow-rad-trampled ; hurt 1 ; message "This heavily\\nradiated snow hurts"

 ladder-down1 -> ladder-down1 ; teleport surface 26 12
 ladder-down2 -> ladder-down2 ; teleport surface 1 0
 ladder-up1 -> ladder-up1 ; teleport entry 0 1
 ladder-up2 -> ladder-up2 ; teleport section2 40 17

-win -> floor ; message "I have found a stash\nto last a lifetime"
+win -> floor ; message "I have found a stash\\nto last a lifetime"

 USERULES

-ladder-down1 eye -> ladder-down1 ; message "Ladder goes to\nthe surface"
-ladder-down2 eye -> ladder-down2 ; message "Ladder goes to\nthe surface"
-ladder-up1 eye -> ladder-up1 ; message "Ladder to\nunderground"
-ladder-up2 eye -> ladder-up2 ; message "Ladder to\nunderground"
+ladder-down1 eye -> ladder-down1 ; message "Ladder goes to\\nthe surface"
+ladder-down2 eye -> ladder-down2 ; message "Ladder goes to\\nthe surface"
+ladder-up1 eye -> ladder-up1 ; message "Ladder to\\nunderground"
+ladder-up2 eye -> ladder-up2 ; message "Ladder to\\nunderground"

 skull eye -> skull ; message "Skull"
 key eye -> key ; message "Key"
@@ -843,7 +843,7 @@
 pickaxe eye -> pickaxe ; message "Pickaxe"
 crowbar eye -> crowbar ; message "Crowbar"
 jane eye -> jane ; message "So you have heard my distress signals"
-mr-white1 eye -> mr-white1 ; message "Mr White looks\nthirsty"
+mr-white1 eye -> mr-white1 ; message "Mr White looks\\nthirsty"
 mr-white2 eye -> mr-white2 ; message "Mr White"
 radx-unpowered eye -> radx-unpowered ; message "Unpowered RADX"
 radx-powered eye -> radx-powered ; message "Powered RADX"
@@ -857,7 +857,7 @@
 Walls eye -> _terrain ; message "Wall"
 bottle-empty eye -> bottle-empty ; message "Empty bottle"
 bottle-water eye -> bottle-water ; message "Bottle of water"
-bottle-rad eye -> bottle-rad ; message "Bottle of irradiated\nwater"
+bottle-rad eye -> bottle-rad ; message "Bottle of irradiated\\nwater"
 purifier eye -> purifier ; message "Empty purifier"
 purifier-water eye -> purifier-water ; message "Purifier"
 purifier-rad eye -> purifier-rad ; message "Purifier"
@@ -866,12 +866,12 @@
 snow eye -> snow ; message "Snow"
 snow-trampled eye -> snow-trampled ; message "Trampled snow"
 snow-rad eye -> snow-rad ; message "Irradiated snow"
-snow-rad-trampled eye -> snow-rad-trampled ; message "Irradiated trampled\nsnow"
+snow-rad-trampled eye -> snow-rad-trampled ; message "Irradiated trampled\\nsnow"
 tree eye -> tree ; message "Dead tree"
 radio1-unpowered eye -> radio1-unpowered ; message "Radio unpowered"
-radio1-powered eye -> radio1-powered ; message "Radio there is a\nfaint signal"
+radio1-powered eye -> radio1-powered ; message "Radio there is a\\nfaint signal"
 radio2-unpowered eye -> radio2-unpowered ; message "Radio unpowered"
-radio2-powered eye -> radio2-powered ; message "Radio the signal is\nstronger here"
+radio2-powered eye -> radio2-powered ; message "Radio the signal is\\nstronger here"

 dirt1 pickaxe -> floor ; consume
 dirt2 pickaxe -> floor ; consume
@@ -892,12 +892,12 @@
 bug crowbar -> floor ; hurt 1 ; consume

 drill-unpowered battery -> floor ; give 1 drill-powered ; consume
-radio1-unpowered battery -> radio1-powered ; consume ; message "You can hear a faint\ndistress signal"
-radio2-unpowered battery -> radio2-powered ; consume ; message "You hear the same\nsignal, but stronger"
+radio1-unpowered battery -> radio1-powered ; consume ; message "You can hear a faint\\ndistress signal"
+radio2-unpowered battery -> radio2-powered ; consume ; message "You hear the same\\nsignal, but stronger"

 floor bottle-water -> floor ; heal 1
-mr-white1 bottle-water -> mr-white2 ; give 1 key , 1 bottle-empty ; consume ; message "Thanks! Here take this\nkey I found! I don't know\nwhat it opens but maybe\nyou will find out"
-mr-white1 bottle-rad -> mr-white1 ; message "This water is\nnot pure"
+mr-white1 bottle-water -> mr-white2 ; give 1 key , 1 bottle-empty ; consume ; message "Thanks! Here take this\\nkey I found! I don't know\\nwhat it opens but maybe\\nyou will find out"
+mr-white1 bottle-rad -> mr-white1 ; message "This water is\\nnot pure"

 snow-rad radx-powered -> snow ; give 1 radx-unpowered ; consume
 snow-rad-trampled radx-powered -> snow-trampled ; give 1 radx-unpowered ; consume


:art: revert since this is more resilient against unexpectation.

--- lib/operator.coffee
+++ lib/operator.coffee
@@ -1058,8 +1058,8 @@
     text = if @target.isLinewise?() then "\n" else ''
     @editor.transact =>
       for selection in @editor.getSelections()
-        selection.insertText(text, autoIndent: true)
-        selection.cursor.moveLeft() if text is "\n"
+        range = selection.insertText(text, autoIndent: true)
+        selection.cursor.moveLeft() unless range.isEmpty()
     super

 class Substitute extends Change


style

style

--- lib/packages.coffee
+++ lib/packages.coffee
@@ -32,7 +32,7 @@
             if err?
               @installError name, err
             else
-              atom.notifications.addSuccess "Juno: Installed Package #{name}"
+              atom.notifications.addSuccess "Juno: Installed package #{name}"
               cb?()
         .catch (err) => @retreiveError name, err


Update for 2.2.0 Perandus, Labyrinth, and Misc.

--- lib/provider.coffee
+++ lib/provider.coffee
@@ -94,7 +94,52 @@
     { snippet: 'Microtransactions' },
     { snippet: 'Jewel' },
     { snippet: '"Divination Card"' },
-    { snippet: 'Card' } #I think this is wrong
+    { snippet: 'Talisman' },#? Not sure
+    { snippet: '"Labyrinth Item"' },
+    { snippet: '"Labyrinth Trinket"' },
+  ]
+
+  quest: [
+    { snippet: '"Alira\'s Amulet"' },
+    { snippet: '"Allflame"' },
+    { snippet: '"Ammonite Glyph"' },
+    { snippet: '"Baleful Gem"' },
+    { snippet: '"Bust of Gaius Sentari"' },
+    { snippet: '"Bust of Hector Titucius"' },
+    { snippet: '"Bust of Marceus Lioneye"' },
+    { snippet: '"Chitus\' Plum"' },
+    { snippet: '"Decanter Spiritus"' },
+    { snippet: '"Deshret\'s Banner"' },
+    { snippet: '"Golden Hand"' },
+    { snippet: '"Golden Page"' },
+    { snippet: '"Haliotis Glyph"' },
+    { snippet: '"Infernal Talc"' },
+    { snippet: '"Kraityn\'s Amulet"' },
+    { snippet: '"Malachai\'s Entrails"' },
+    { snippet: '"Malachai\'s Heart"' },
+    { snippet: '"Malachai\'s Lungs"' },
+    { snippet: '"Maligaro\'s Spike"' },
+    { snippet: '"Medicine Chest"' },
+    { snippet: '"Oak\'s Amulet"' },
+    { snippet: '"Ribbon Spool"' },
+    { snippet: '"Roseus Glyph"' },
+    { snippet: '"Thaumetic Emblem"' },
+    { snippet: '"The Eye of Desire"' },
+    { snippet: '"The Eye of Fury"' },
+    { snippet: '"Tolman\'s Bracelet"' },
+    { snippet: '"Tower Key"' },
+    #Labyrinth
+    { snippet: '"Silver Key"' },
+    { snippet: '"Golden Key"' },
+    { snippet: '"Treasure Key"' },
+    { snippet: '"Bane of the Loyal"' },
+    { snippet: '"Cogs of Disruption"' },
+    { snippet: '"Cube of Absorption"' },
+    { snippet: '"Heart of the Gargoyle"' },
+    { snippet: '"Orb of Elemental Essence"' },
+    { snippet: '"Portal Shredder"' },
+    { snippet: '"Rod of Detonation"' },
+    { snippet: '"Sand of Eternity"' },
   ]

   amulets: [
@@ -175,6 +220,9 @@
   ]

   currency: [
+    { snippet: '"Albino Rhoa Feather"' },
+    { snippet: '"Alchemy Shard"' },
+    { snippet: '"Alteration Shard"' },
     { snippet: '"Armourer\'s Scrap"' },
     { snippet: '"Blacksmith\'s Whetstone"' },
     { snippet: '"Blessed Orb"' },
@@ -198,20 +246,28 @@
     { snippet: '"Orb of Transmutation"' },
     { snippet: '"Portal Scroll"' },
     { snippet: '"Regal Orb"' },
+    { snippet: '"Scroll Fragment"' },
     { snippet: '"Scroll of Wisdom"' },
+    { snippet: '"Transmutation Shard"' },
     { snippet: '"Vaal Orb"' }
+    #Perandus
+    { snippet: '"Perandus Coin"' },
+    #Phrophecy
+    { snippet: '"Silver Coin"' },
   ]

   skills: [
     #strength/red Active
     { snippet: '"Abyssal Cry"' },
     { snippet: 'Anger' },
+    { snippet: '"Ancestral Protector"' },
     { snippet: '"Animate Guardian"' },
     { snippet: 'Cleave' },
     { snippet: '"Decoy Totem"' },
     { snippet: 'Determination' },
     { snippet: '"Devouring Totem"' },
     { snippet: '"Dominating Blow"' },
+    { snippet: '"Earthquake"' },
     { snippet: '"Enduring Cry"' },
     { snippet: '"Flame Totem"' },
     { snippet: '"Glacial Hammer"' },
@@ -233,6 +289,8 @@
     { snippet: '"Shockwave Totem"' },
     { snippet: '"Static Strike"' },
     { snippet: '"Summon Flame Golem"' },
+    { snippet: '"Summon Stone Golem"' },
+    { snippet: '"Sunder"' },
     { snippet: 'Sweep' },
     { snippet: 'Vengeance' },
     { snippet: '"Vigilant Strike"' },
@@ -303,6 +361,7 @@
     { snippet: 'Hatred' },
     { snippet: '"Herald of Ice"' },
     { snippet: '"Ice Shot"' },
+    { snippet: '"Ice Trap"' },
     { snippet: '"Lightning Arrow"' },
     { snippet: '"Lightning Strike"' },
     { snippet: '"Mirror Arrow"' },
@@ -335,6 +394,7 @@
     { snippet: '"Cast on Death"' },
     { snippet: 'Chain' },
     { snippet: '"Chance to Flee"' },
+    { snippet: '"Cluster Traps"' },
     { snippet: '"Cold Penetration"' },
     { snippet: '"Culling Strike"' },
     { snippet: 'Enhance' },
@@ -355,6 +415,7 @@
     { snippet: '"Slower Projectiles"' },
     { snippet: 'Trap' },
     { snippet: '"Trap and Mine Damage"' },
+    { snippet: '"Trap Cooldown"' },
     { snippet: '"Void Manipulation"' },
     #dexterity/green vaal
     { snippet: '"Vaal Burning Arrow"' },
@@ -393,6 +454,7 @@
     { snippet: '"Flesh Offering"' },
     { snippet: '"Freezing Pulse"' },
     { snippet: 'Frostbite' },
+    { snippet: '"Frost Bomb"' },
     { snippet: '"Frost Wall"' },
     { snippet: '"Glacial Cascade"' },
     { snippet: '"Herald of Thunder"' },
@@ -404,6 +466,7 @@
     { snippet: '"Lightning Trap"' },
     { snippet: '"Lightning warp"' },
     { snippet: '"Magma Orb"' },
+    { snippet: '"Orb of Storms"' },
     { snippet: '"Power Siphon"' },
     { snippet: '"Purity of Elements"' },
     { snippet: '"Purity of Lightning"' },
@@ -429,6 +492,7 @@
     { snippet: '"Concentrated Effect"' },
     { snippet: '"Controlled Destruciton"' },
     { snippet: '"Curse on Hit"' },
+    { snippet: '"Elemental Focus"' },
     { snippet: '"Elemental Proliferation"' },
     { snippet: 'Enlighten' },
     { snippet: '"Faster Casting"' },
@@ -438,6 +502,7 @@
     { snippet: 'Innervate' },
     { snippet: '"Item Rarity"' },
     { snippet: '"Lightning Penetration"' },
+    { snippet: 'Minefield' },
     { snippet: '"Minion and Totem Elemental Resistance"' },
     { snippet: '"Minion Damage"' },
     { snippet: '"Minion Life"' },
@@ -473,6 +538,7 @@
   ]

   maps: [
+    #Tier 1
     { snippet: '"Crypt Map"' },
     { snippet: '"Desert Map"' },
     { snippet: '"Dunes Map"' },
@@ -480,6 +546,7 @@
     { snippet: '"Grotto Map"' },
     { snippet: '"Pit Map"' },
     { snippet: '"Tropical Island Map"' },
+    #Tier 2
     { snippet: '"Arcade Map"' },
     { snippet: '"Cemetery Map"' },
     { snippet: '"Channel Map"' },
@@ -487,7 +554,7 @@
     { snippet: '"Sewer Map"' },
     { snippet: '"Thicket Map"' },
     { snippet: '"Wharf Map"' },
-    { snippet: '"The Apex of Sacrifice"' },
+    #Tier 3
     { snippet: '"Ghetto Map"' },
     { snippet: '"Mud Geyser Map"' },
     { snippet: '"Museum Map"' },
@@ -495,6 +562,7 @@
     { snippet: '"Reef Map"' },
     { snippet: '"Spider Lair Map"' },
     { snippet: '"Vaal Pyramid Map"' },
+    #Tier 4
     { snippet: '"Arena Map"' },
     { snippet: '"Overgrown Shrine Map"' },
     { snippet: '"Promenade Map"' },
@@ -502,57 +570,71 @@
     { snippet: '"Shore Map"' },
     { snippet: '"Spider Forest Map"' },
     { snippet: '"Tunnel Map"' },
+    #Tier 5
     { snippet: '"Bog Map"' },
     { snippet: '"Coves Map"' },
     { snippet: '"Graveyard Map"' },
     { snippet: '"Pier Map"' },
     { snippet: '"Underground Sea Map"' },
     { snippet: '"Villa Map"' },
+    #Tier 6
     { snippet: '"Arachnid Nest Map"' },
     { snippet: '"Catacomb Map"' },
     { snippet: '"Colonnade Map"' },
     { snippet: '"Dry Woods Map"' },
     { snippet: '"Strand Map"' },
     { snippet: '"Temple Map"' },
+    #Tier 7
     { snippet: '"Jungle Valley Map"' },
     { snippet: '"Labyrinth Map"' },
-    { snippet: '"Mine Map"' },
+    { snippet: '"Abandoned Cavern Map"' },
     { snippet: '"Torture Chamber Map"' },
     { snippet: '"Waste Pool Map"' },
+    #Tier 8
     { snippet: '"Canyon Map"' },
     { snippet: '"Cells Map"' },
     { snippet: '"Dark Forest Map"' },
     { snippet: '"Dry Peninsula Map"' },
     { snippet: '"Orchard Map"' },
+    #Tier 9
     { snippet: '"Arid Lake Map"' },
     { snippet: '"Gorge Map"' },
     { snippet: '"Malformation Map"' },
     { snippet: '"Residence Map"' },
     { snippet: '"Underground River Map"' },
+    #Tier 10
     { snippet: '"Bazaar Map"' },
+    { snippet: '"Chateau Map"' },
     { snippet: '"Necropolis Map"' },
     { snippet: '"Plateau Map"' },
     { snippet: '"Volcano Map"' },
+    #Tier 11
     { snippet: '"Academy Map"' },
     { snippet: '"Crematorium Map"' },
     { snippet: '"Precinct Map"' },
     { snippet: '"Springs Map"' },
+    #Tier 12
     { snippet: '"Arsenal Map"' },
     { snippet: '"Overgrown Ruin Map"' },
     { snippet: '"Shipyard Map"' },
     { snippet: '"Village Ruin Map"' },
-    { snippet: '"The Alluring Abyss"' },
+    #Tier 13
     { snippet: '"Courtyard Map"' },
     { snippet: '"Excavation Map"' },
     { snippet: '"Wasteland Map"' },
     { snippet: '"Waterways Map"' },
-    { snippet: '"Maze Map"' },
+    #Tier 14
+    { snippet: '"Conservatory Map"' },
     { snippet: '"Palace Map"' },
     { snippet: '"Shrine Map"' },
     { snippet: '"Vaal Temple Map"' },
+    #Tier 15
     { snippet: '"Abyss Map"' },
     { snippet: '"Colosseum Map"' },
     { snippet: '"Core Map"' }
+    #Misc
+    { snippet: '"The Apex of Sacrifice"' },
+    { snippet: '"The Alluring Abyss"' },
   ]

   flasks: [
@@ -599,6 +681,12 @@
     { snippet: '"Diamond Flask"' },
     { snippet: '"Jade Flask"' },
     { snippet: '"Quartz Flask"' }
+    { snippet: '"Basalt Flask"' },
+    { snippet: '"Aquamarine Flask"' },
+    { snippet: '"Stibnite Flask"' },
+    { snippet: '"Sulphur Flask"' },
+    { snippet: '"Silver Flask"' },
+    { snippet: '"Bismuth Flask"' },
   ]

   bows: [
@@ -1352,7 +1440,7 @@
       suggestions = @classes

     if 'filter.basetype.poe' in scopeDescriptor.scopes and prefix not in @excludedPrefixes
-      suggestions = suggestions.concat(@amulets, @belts, @rings, @currency, @skills, @jewels, @maps, @flasks,
+      suggestions = suggestions.concat(@amulets, @talisman, @belts, @rings, @currency, @skills, @jewels, @maps, @flasks,
                                        @bows, @claws, @daggers, @oneHandAxe, @oneHandMace, @oneHandSword, @thrustingOneHandSword, @sceptre, @staff, @twoHandAxe, @twoHandMace, @twoHandSword, @wand,
                                        @bodyArmour, @boots, @gloves, @helmets, @shields)


3d: only show the plot once the data is loaded and we're rendering it; so we don't get a blank box on loading a worksheet.  Doesn't address fundamental issue that plots only appear on evaluation.

--- salvus/page/3d.coffee
+++ salvus/page/3d.coffee
@@ -442,6 +442,7 @@
             mouseover : true
         #console.log('anim', @opts.element.length, @opts.element.is(":visible"))

+        @opts.element.show()
         if not @opts.element.is(":visible")
             # check again after a delay
             setTimeout((() => @animate(opts)), 1500)
@@ -466,6 +467,8 @@

     render_scene: (force=false) =>
         #console.log('render', @opts.element.length)
+        @opts.element.show()
+
         if @controls?
             @controls?.update()
         else
@@ -500,7 +503,7 @@
 $.fn.salvus_threejs = (opts={}) ->
     @each () ->
         elt = $(this)
-        e = $(".salvus-3d-templates .salvus-3d-viewer").clone()
+        e = $(".salvus-3d-templates .salvus-3d-viewer").clone().hide()
         elt.empty().append(e)
         opts.element = e
         f = () -> elt.data('salvus-threejs', new SalvusThreeJS(opts))


also snapshot when migrating

also snapshot when migrating

--- salvus/storage.coffee
+++ salvus/storage.coffee
@@ -3706,6 +3706,12 @@
         (cb) ->
             if not host?
                 cb(); return
+            dbg("take a snapshot")
+            client.snapshot
+                cb   : cb
+        (cb) ->
+            if not host?
+                cb(); return
             dbg("now save and close project")
             client.close(cb:cb)
         (cb) ->


Use process.platform not os.platform

Use process.platform not os.platform

--- lib/wakatime.coffee
+++ lib/wakatime.coffee
@@ -195,7 +195,7 @@
         statusBarIcon?.setTitle(msg)

 getUserHome = ->
-  process.env[if os.platform == 'win32' then 'USERPROFILE' else 'HOME'] || ''
+  process.env[if process.platform == 'win32' then 'USERPROFILE' else 'HOME'] || ''

 loadDependencies = ->
   fs = require 'fs'
@@ -308,11 +308,11 @@
 installCLI = (callback) ->
   log.debug 'Downloading wakatime-cli...'
   statusBarIcon?.setStatus('downloading wakatime-cli...')
-  ext = if os.platform == 'win32' then '.exe' else ''
+  ext = if process.platform == 'win32' then '.exe' else ''
   url = s3BucketUrl() + 'wakatime' + ext
   localFile = __dirname + path.sep + 'wakatime-cli' + ext
   downloadFile(url, localFile, () ->
-    if os.platform != 'win32'
+    if process.platform != 'win32'
       fs.chmodSync(localFile, 0o755)
     if callback?
       callback()
@@ -324,7 +324,7 @@

 s3BucketUrl = () ->
   prefix = 'https://wakatime-cli.s3-us-west-2.amazonaws.com/'
-  p = os.platform()
+  p = process.platform()
   return prefix + 'mac-x86-64/' if p == 'darwin'
   return prefix + 'windows-x86-' + architecture() + '/' if p == 'win32'
   return prefix + 'linux-x86-64/'


For-Tag shouldn't overshadow `name`

For-Tag shouldn't overshadow `name`

--- src/liquid/tags/for.coffee
+++ src/liquid/tags/for.coffee
@@ -58,7 +58,7 @@
     if match
       @variableName = match[1]
       @collectionName = match[2]
-      @name = "#{match[1]}=#{match[2]}"
+      @registerName = "#{match[1]}=#{match[2]}"
       @reversed = match[3]
       @attributes = {}

@@ -81,7 +81,7 @@
       return @renderElse(context) unless collection and collection.forEach

       from = if @attributes.offset == "continue"
-        Number(context.registers["for"][@name]) or 0
+        Number(context.registers["for"][@registerName]) or 0
       else
         Number(@attributes.offset) or 0

@@ -97,13 +97,13 @@
       length = segment.length

       # Store our progress through the collection for the continue flag
-      context.registers["for"][@name] = from + segment.length
+      context.registers["for"][@registerName] = from + segment.length

       context.stack =>
         Promise.reduce(segment, (output, item, index) =>
           context.set @variableName, item
           context.set "forloop",
-            name    : @name
+            name    : @registerName
             length  : length
             index   : index + 1
             index0  : index,


Tests: add event emitter tests

Tests: add event emitter tests

--- test/core/eventemitter.test.coffee
+++ test/core/eventemitter.test.coffee
@@ -1,7 +1,323 @@
-{assert} = require('chai')
+{ assert } = require('chai')
 KDEventEmitter = require('../../src/core/eventemitter')

 describe 'KDEventEmitter', ->

   it 'exists', ->
     assert.ok(KDEventEmitter)
+
+  describe 'KDEventEmitter.registerStaticEmitter', ->
+
+    it 'creates events holder', ->
+
+      KDEventEmitter.registerStaticEmitter()
+      assert.isDefined KDEventEmitter._e
+
+  describe 'KDEventEmitter.emit', ->
+
+    context "when event holder isn't defined", ->
+
+      beforeEach ->
+        # This is suspicious
+        # TODO: Talk about it ~Umut
+        KDEventEmitter._e = null
+
+      it 'throws an error', ->
+
+        assert.throws (-> KDEventEmitter.emit()), /Static events are not enabled/
+
+    context 'when event holder is defined', ->
+
+      beforeEach ->
+        KDEventEmitter.registerStaticEmitter()
+
+      it 'registers an event with the name', ->
+        KDEventEmitter.emit 'anEvent'
+        assert.isDefined KDEventEmitter._e['anEvent']
+
+      it 'creates event holder if it is not created', ->
+        KDEventEmitter.emit 'anEvent'
+        assert.isDefined KDEventEmitter._e['anEvent']
+
+      it 'calls all of the listeners', ->
+        array = []
+        KDEventEmitter._e['anEvent'] = []
+        KDEventEmitter._e['anEvent'].push(=> array.push( 1 ))
+        KDEventEmitter._e['anEvent'].push(=> array.push( 2 ))
+
+        KDEventEmitter.emit 'anEvent'
+
+        assert.equal array.length, 2
+
+      it 'returns itself for chaining', ->
+        ret = KDEventEmitter.emit 'anEvent'
+        assert.equal ret, KDEventEmitter
+
+  describe 'KDEventEmitter.on', ->
+
+    context 'when listener is not a function', ->
+
+      it 'throws an error', ->
+        assert.throws (-> KDEventEmitter.on()), /listener is not a function/
+        assert.throws (-> KDEventEmitter.on('a', 'b')), /listener is not a function/
+        assert.throws (-> KDEventEmitter.on('a', 1)), /listener is not a function/
+        assert.throws (-> KDEventEmitter.on('a', {})), /listener is not a function/
+        assert.throws (-> KDEventEmitter.on('a', [])), /listener is not a function/
+        assert.throws (-> KDEventEmitter.on('a', yes)), /listener is not a function/
+
+    context "when event holder isn't defined", ->
+
+      beforeEach ->
+        # This is suspicious
+        # TODO: Talk about it ~Umut
+        KDEventEmitter._e = null
+
+      it 'throws an error', ->
+        onFn = -> KDEventEmitter.on 'a', -> 'dummy'
+        assert.throws onFn, /Static events are not enabled/
+
+    context "when requirements are ok", ->
+
+      beforeEach ->
+        KDEventEmitter.registerStaticEmitter()
+
+      it 'emits an event with listener', ->
+        listener = -> yes
+        KDEventEmitter.on 'anEvent', listener
+
+        assert.isDefined KDEventEmitter._e['newListener']
+
+      it 'registers an event', ->
+        listener = -> yes
+        KDEventEmitter.on 'anEvent', listener
+
+        assert.isDefined KDEventEmitter._e['anEvent']
+        assert.equal KDEventEmitter._e['anEvent'][0], listener
+
+      it 'registers multiple events', ->
+        listener = -> yes
+        KDEventEmitter.on ['anEvent', 'otherEvent'], listener
+
+        assert.isDefined KDEventEmitter._e['anEvent']
+        assert.equal KDEventEmitter._e['anEvent'][0], listener
+
+        assert.isDefined KDEventEmitter._e['otherEvent']
+        assert.equal KDEventEmitter._e['otherEvent'][0], listener
+
+      it 'returns itself for chaining', ->
+        listener = -> yes
+        onFn = KDEventEmitter.on 'anEvent', listener
+
+        assert.equal onFn, KDEventEmitter
+
+
+  describe 'KDEventEmitter.off', ->
+
+    beforeEach -> KDEventEmitter.registerStaticEmitter()
+
+    it 'emits an event', ->
+      listener = -> yes
+      KDEventEmitter.on 'anEvent', listener
+      KDEventEmitter.off 'anEvent', listener
+
+      assert.isDefined KDEventEmitter._e['listenerRemoved']
+
+    it 'unregisters an event', ->
+      listener = -> yes
+      KDEventEmitter.on 'anEvent', listener
+      KDEventEmitter.off 'anEvent', listener
+
+      assert.equal KDEventEmitter._e['anEvent'].length, 0
+
+    it "doesn't unregister a listener doesn't exist on that key", ->
+      listener = -> yes
+      anotherListener = -> no
+
+      KDEventEmitter.on 'anEvent', listener
+      KDEventEmitter.off 'anEvent', anotherListener
+
+      assert.deepEqual KDEventEmitter._e['anEvent'], [ listener ]
+
+    it 'unregisters multiple events', ->
+      listener = -> yes
+      KDEventEmitter.on ['a', 'b'], listener
+      KDEventEmitter.off ['a', 'b'], listener
+
+      assert.equal KDEventEmitter._e['a'].length, 0
+      assert.equal KDEventEmitter._e['b'].length, 0
+
+    it 'returns itself for chaining', ->
+      listener = -> yes
+      KDEventEmitter.on 'anEvent', listener
+      offFn = KDEventEmitter.off 'anEvent', listener
+
+      assert.equal offFn, KDEventEmitter
+
+
+  context 'Instance Methods', ->
+
+    describe 'constructor', ->
+
+      it 'creates events holder', ->
+        emitter = new KDEventEmitter
+        assert.isDefined emitter._e
+
+      it 'defaults maxlisteners to 10', ->
+        emitter = new KDEventEmitter
+        assert.equal emitter._maxListeners, 10
+
+      it 'sets max listeners to option', ->
+        emitter = new KDEventEmitter maxListeners: 30
+        assert.equal emitter._maxListeners, 30
+
+      it "doesn't set max listeners to a negative value", ->
+        emitter = new KDEventEmitter maxListeners: -1
+        assert.equal emitter._maxListeners, 10
+
+    describe 'emit', ->
+
+      it 'creates an event holder', ->
+        emitter = new KDEventEmitter
+        emitter.emit 'anEvent'
+
+        assert.isDefined emitter._e['anEvent']
+
+      it 'calls listeners', ->
+        array = []
+        emitter = new KDEventEmitter
+        emitter._e = {}
+        emitter._e['anEvent'] = []
+        emitter._e['anEvent'].push(=> array.push( 1 ))
+        emitter._e['anEvent'].push(=> array.push( 2 ))
+
+        emitter.emit 'anEvent'
+
+        assert.deepEqual array, [1, 2]
+
+      it 'returns itself for chaining', ->
+        emitter = new KDEventEmitter
+        emitResult = emitter.emit 'anEvent'
+
+        assert.equal emitResult, emitter
+
+    describe 'on', ->
+
+      context 'when listener is not a function', ->
+
+        it 'throws an error', ->
+          emitter = new KDEventEmitter
+          assert.throws (-> emitter.on()), /listener is not a function/
+          assert.throws (-> emitter.on('a', 'b')), /listener is not a function/
+          assert.throws (-> emitter.on('a', 1)), /listener is not a function/
+          assert.throws (-> emitter.on('a', {})), /listener is not a function/
+          assert.throws (-> emitter.on('a', [])), /listener is not a function/
+          assert.throws (-> emitter.on('a', yes)), /listener is not a function/
+
+      it 'emits an event with listener', ->
+        listener = -> yes
+        emitter  = new KDEventEmitter
+
+        emitter.on 'anEvent', listener
+
+        assert.isDefined emitter._e['newListener']
+
+      it 'registers an event', ->
+        array    = []
+        listener = (number) -> array.push number
+        emitter  = new KDEventEmitter
+
+        emitter.on 'anEvent', listener
+
+        emitter.emit 'anEvent', 1
+        emitter.emit 'anEvent', 2
+
+        assert.deepEqual array, [ 1, 2 ]
+
+      it 'registers multiple events', ->
+        listener = -> yes
+        emitter  = new KDEventEmitter
+
+        emitter.on ['anEvent', 'otherEvent'], listener
+
+        assert.isDefined emitter._e['anEvent']
+        assert.equal emitter._e['anEvent'][0], listener
+
+        assert.isDefined emitter._e['otherEvent']
+        assert.equal emitter._e['otherEvent'][0], listener
+
+      it 'returns itself for chaining', ->
+        listener = -> yes
+        emitter = new KDEventEmitter
+
+        onFn = emitter.on 'anEvent', listener
+
+        assert.equal onFn, emitter
+
+    describe 'off', ->
+
+      it 'emits an event', ->
+        listener = -> yes
+        emitter = new KDEventEmitter
+
+        emitter.on 'anEvent', listener
+        emitter.off 'anEvent', listener
+
+        assert.isDefined emitter._e['listenerRemoved']
+
+      it 'unregisters an event', ->
+        listener = -> yes
+        emitter = new KDEventEmitter
+
+        emitter.on 'anEvent', listener
+        emitter.off 'anEvent', listener
+
+        assert.equal emitter._e['anEvent'].length, 0
+
+      it "doesn't unregister a listener doesn't exist on that key", ->
+        emitter         = new KDEventEmitter
+        listener        = -> yes
+        anotherListener = -> no
+
+        emitter.on 'anEvent', listener
+        emitter.off 'anEvent', anotherListener
+
+        assert.deepEqual emitter._e['anEvent'], [ listener ]
+
+      it 'unregisters multiple events', ->
+        listener = -> yes
+        emitter  = new KDEventEmitter
+
+        emitter.on ['a', 'b'], listener
+        emitter.off ['a', 'b'], listener
+
+        assert.equal emitter._e['a'].length, 0
+        assert.equal emitter._e['b'].length, 0
+
+      it 'returns itself for chaining', ->
+        listener = -> yes
+        emitter  = new KDEventEmitter
+
+        emitter.on 'anEvent', listener
+        offFn = emitter.off 'anEvent', listener
+
+        assert.equal offFn, emitter
+
+    describe 'once', ->
+
+      it 'registers an event to be called only once', ->
+        array    = []
+        emitter  = new KDEventEmitter
+
+        emitter.once 'anEvent', (number) -> array.push number
+
+        emitter.emit 'anEvent', 1
+        emitter.emit 'anEvent', 2
+
+        assert.deepEqual array, [ 1 ]
+
+      it 'returns itself for chaining', ->
+        emitter = new KDEventEmitter
+        onceResult = emitter.once 'anEvent', (->)
+
+        assert.equal onceResult, emitter
+


Simplify markup selector for codinglove.

Selector is compatible with home and random pages.

--- src/codinglove.coffee
+++ src/codinglove.coffee
@@ -71,7 +71,7 @@

     img_src = get_meme_image(body, ".post img")

-    txt = get_meme_txt(body, ".post .centre h3")
+    txt = get_meme_txt(body, ".post h3")

     response_handler "#{txt}"
     response_handler "#{img_src}"


Prefer static method over instance method.

--- src/coffee/run.coffee
+++ src/coffee/run.coffee
@@ -24,7 +24,7 @@

 promises = Q.all [readCsvPromise(argv.types), readCsvPromise(argv.attributes)]

-promises.spread (types, attributes) ->
+Q.spread promises, (types, attributes) ->
   generator = new ProductTypeGenerator
   generator.run types, attributes, (success) ->
     process.exit 1 unless success


fix new little bug that prevented project snap listing retry / error reporting

--- salvus/client.coffee
+++ salvus/client.coffee
@@ -1179,7 +1179,7 @@
             tries = 0
             cb = opts.cb
             # TODO: Try multiple times since server just gives an error on backend failure.
-            f = (err, result) ->
+            f = (err, result) =>
                 if err and tries < 2
                     tries += 1
                     @project_snap_listing(opts)


Reset launch page

Reset launch page

--- gruntfile.coffee
+++ gruntfile.coffee
@@ -126,7 +126,7 @@
         options:
           port: '3000'
           open:
-            target: 'http://localhost:<%= connect.server.options.port %>/html6/tests/'
+            target: 'http://localhost:<%= connect.server.options.port %>'

     watch:
       options:


jsonp

jsonp

--- src/conekta.js.coffee
+++ src/conekta.js.coffee
@@ -3,13 +3,13 @@
 window.conekta =
   _helpers:
     x_domain_post:(params)->
-      #type = 'POST'
-      #dataType = 'JSON'
-      #if navigator.userAgent.match(/MSIE [67]+/)
-      dataType = 'JSONP'
-      type = 'GET'
-      params.url = params.jsonp_url || params.url
-      params.data.auth_token = conekta.getPublishableToken()
+      type = 'POST'
+      dataType = 'JSON'
+      if navigator.userAgent.match(/MSIE [67]+/)
+        dataType = 'JSONP'
+        type = 'GET'
+        params.url = params.jsonp_url || params.url
+        params.data.auth_token = conekta.getPublishableToken()

       jQuery.ajax(
         url: 'https://paymentsapi-dev.herokuapp.com/' + params.url + '.json'


Add parentheses

Add parentheses

--- scripts/bungie.coffee
+++ scripts/bungie.coffee
@@ -39,17 +39,17 @@
     else
       data['activityKey'] = activityKey

-    getPublicWeeklyActivity(res, data.activityKey).then (activityDetails) -> {
+    getPublicWeeklyActivity(res, data.activityKey).then((activityDetails) -> {

       payload =
         message: res.message
         attachments: [dataHelper.parseActivityDetails(activityDetails)]

       robot.emit 'slack-attachment', payload
-    }.fail (err) -> {
+    }).fail((err) -> {

       sendError(robot, res, err)
-    }
+    })

     # # interprets input based on length
     # # if 3 elements, assume: gamertag, network, bucket


Shim fs as empty.

Shim fs as empty.

--- tasks/config/webpack.config.coffee
+++ tasks/config/webpack.config.coffee
@@ -68,3 +68,11 @@
       R: 'ramda'
     })
   ]
+  node:
+    console: false
+    process: true
+    global: true
+    Buffer: true
+    __filename: "mock"
+    __dirname: "mock"
+    fs: 'empty'


[grunt] better argument names for Nunjucks filters and global functions; some fixes for docs

--- tasks/grunt-nunjucks-2-html.coffee
+++ tasks/grunt-nunjucks-2-html.coffee
@@ -215,11 +215,11 @@

           ###*
            * Log specified to Grunt's console for debug purposes
-           * @param {*} variable Anything we want to log to console
+           * @param {*} input Anything we want to log to console
            * @return {string} Logs to Grunt console
           ###
-          env.addGlobal 'log', (variable) ->
-            console.log(variable)
+          env.addGlobal 'log', (input) ->
+            console.log(input)

           ###*
            * Get list of files or directories inside specified directory
@@ -304,12 +304,12 @@
           ###*
           * Load plural string from current locale
           * @param {string} string          String, which should be loaded
-          * @param {string} stringPlural    Plural form of string
+          * @param {string} pluralString    Plural form of string
           * @param {number} count           Count for detecting correct plural form
           * @return {string} Pluralized and translated into current locale string
           ###
-          env.addGlobal 'ngettext', (string, stringPlural, count) ->
-            i18n.dngettext(currentLocale, string, stringPlural, count)
+          env.addGlobal 'ngettext', (string, pluralString, count) ->
+            i18n.dngettext(currentLocale, string, pluralString, count)

           ###*
            * Load plural string from specified domain
@@ -317,13 +317,13 @@
            *                                        `en-US:other:inner` will load from `en-US/other/inner.po`
            *                                        `:other` will load from `{{currentLocale}}/other.po`
            * @param {string} string                 String, which should be loaded
-           * @param {string} stringPlural           Plural form of string
+           * @param {string} pluralString           Plural form of string
            * @param {number} count                  Count for detecting correct plural form
            * @return {string} Pluralized and translated into specified loca stringle
           ###
-          env.addGlobal 'dngettext', (domain = currentLocale, string, stringPlural, count) ->
+          env.addGlobal 'dngettext', (domain = currentLocale, string, pluralString, count) ->
             domain = if domain.charAt(0) == ':' then currentLocale + domain else domain
-            i18n.dngettext(domain, string, stringPlural, count)
+            i18n.dngettext(domain, string, pluralString, count)

           ###*
           * Load string of specific context from current locale
@@ -351,12 +351,12 @@
           * Load plural string of specific context from current locale
           * @param {string} context         Context of curret string
           * @param {string} string          String, which should be loaded
-          * @param {string} stringPlural    Plural form of string
+          * @param {string} pluralString    Plural form of string
           * @param {number} count           Count for detecting correct plural form
           * @return {string} Pluralized and translated into current locale string
           ###
-          env.addGlobal 'npgettext', (context, string, stringPlural, count) ->
-            i18n.dnpgettext(currentLocale, context, string, stringPlural, count)
+          env.addGlobal 'npgettext', (context, string, pluralString, count) ->
+            i18n.dnpgettext(currentLocale, context, string, pluralString, count)

           ###*
            * Load plural string of specific context from specified domain
@@ -365,13 +365,13 @@
            *                                        `:other` will load from `{{currentLocale}}/other.po`
            * @param {string} context                Context of curret string
            * @param {string} string                 String, which should be loaded
-           * @param {string} stringPlural           Plural form of string
+           * @param {string} pluralString           Plural form of string
            * @param {number} count                  Count for detecting correct plural form
            * @return {string} Pluralized and translated into specified loca stringle
           ###
-          env.addGlobal 'dnpgettext', (domain = currentLocale, context, string, stringPlural, count) ->
+          env.addGlobal 'dnpgettext', (domain = currentLocale, context, string, pluralString, count) ->
             domain = if domain.charAt(0) == ':' then currentLocale + domain else domain
-            i18n.dnpgettext(domain, context, string, stringPlural, count)
+            i18n.dnpgettext(domain, context, string, pluralString, count)

           # =======
           # Filters
@@ -381,7 +381,7 @@
            * Replaces last array element with new value
            * @warn Mutates array
            * @param {array} array Target array
-           * @param {*} value     New value
+           * @param {*}     value New value
            * @return {array} Mutated array
           ###
           env.addFilter 'popIn', (array, value) ->
@@ -393,7 +393,7 @@
            * Adds value to the end of an array
            * @warn Mutates array
            * @param {array} array Target array
-           * @param {*} value     Value to be pushed in
+           * @param {*}     value Value to be pushed in
            * @return {array} Mutated array
           ###
           env.addFilter 'pushIn', (array, value) ->
@@ -410,9 +410,9 @@

           ###*
            * Replace placeholders with provided values
-           * @param {string} string                       String in which should be made replacement
-           * @param {string|object|array} placeholders... List of placeholders, object with named
-           *                                              placeholders or arrays of placeholders
+           * @param {string}                     string          String in which should be made replacement
+           * @param {number|string|object|array} placeholders... List of arguments as placeholders, object with named
+           *                                                     placeholders or arrays of placeholders
            * @return {string} String with replaced placeholders
           ###
           env.addFilter 'template', (string, placeholders...) ->


Do not pass additional arguments to transaction continuation.

This fixes a tiny issue where the continuation that was passed to the `transaction` method
would be called with one more argument than expected. This argument was always `undefined`,
and this bug was only affecting the `arguments.length` value.

--- src/connection.coffee
+++ src/connection.coffee
@@ -834,7 +834,7 @@

     txDone = (err, done) =>
       args = []
-      args.push(arguments[i]) for i in [2..arguments.length]
+      args.push(arguments[i]) for i in [2...arguments.length]

       if err
         if @inTransaction


Fix bottom gutter bug

Fix bottom gutter bug

--- src/controller.coffee
+++ src/controller.coffee
@@ -3603,7 +3603,8 @@
   # jammed up against the edge of the screen.
   #
   # Default this extra space to fontSize (approx. 1 line).
-  @mainScrollerStuffing.style.height = "#{bounds.bottom() + (@options.extraBottomHeight ? @fontSize)}px"
+  @mainScrollerStuffing.style.height = "#{bounds.bottom() +
+    (@options.extraBottomHeight ? @fontSize)}px"

 hook 'redraw_palette', 0, ->
   bounds = new @draw.NoRectangle()
@@ -4287,7 +4288,9 @@

 Editor::resizeGutter = ->
   @gutter.style.width = @aceEditor.renderer.$gutterLayer.gutterWidth + 'px'
-  @gutter.style.height = "#{Math.max @dropletElement.offsetHeight, @view.getViewNodeFor(@tree).totalBounds?.height ? 0}px"
+  @gutter.style.height = "#{Math.max(@dropletElement.offsetHeight,
+    (@view.getViewNodeFor(@tree).totalBounds?.bottom?() ? 0) +
+    (@options.extraBottomHeight ? @fontSize))}px"

 Editor::addLineNumberForLine = (line) ->
   treeView = @view.getViewNodeFor @tree
@@ -4358,7 +4361,7 @@
       delete @lineNumberTags[line]

   if changedBox
-    @gutter.style.height = "#{Math.max @mainScroller.offsetHeight, treeView.totalBounds.height}px"
+    @resizeGutter()

 Editor::setPaletteWidth = (width) ->
   @paletteWrapper.style.width = width + 'px'


Fix deadline time

Fix deadline time

--- scripts/thesis.coffee
+++ scripts/thesis.coffee
@@ -10,7 +10,9 @@
 module.exports = (robot) ->
   deadlineIntervalId = null
   deadline = new Date()
-  deadline.setDate(15, 8, 2014)
+  deadline.setDate(15)
+  deadline.setMonth(7)
+  deadline.setFullYear(2014)
   deadline.setUTCHours(14)
   deadline.setMinutes(0)
   dayDurationMilli = 24 * 3600 * 1000
@@ -20,6 +22,9 @@
   printTimeBeforeDeadline = (msg) ->
     now = new Date()
     durationBeforeDeadline = deadline.getTime() - now.getTime()
+    console.log(now)
+    console.log(deadline)
+    console.log(durationBeforeDeadline)
     if durationBeforeDeadline > 0
       nbOfDays = durationBeforeDeadline / dayDurationMilli
       nbOfHours = (durationBeforeDeadline % dayDurationMilli) / hourDurationMilli


CLI/new making it generate a basic unit test structure

--- cli/commands/new.coffee
+++ cli/commands/new.coffee
@@ -74,6 +74,57 @@
   print " - src/#{filename}"
   fs.writeFileSync("#{directory}/src/#{filename}", patch_source(source))

+  print " - test/"
+  fs.mkdirSync("#{directory}/test", 0o0755)
+
+  filename = "#{placeholders.projectfile}_test.#{
+    if use_coffee then 'coffee' else 'js'
+  }"
+
+  source = if use_coffee then """
+  #
+  # Project's main unit test
+  #
+  # Copyright (C) %{year} %{username}
+  #
+  {Test, assert} = require('lovely')
+
+  describe "%{projectunit}", ->
+    %{projectunit} = null
+
+    before Test.load(module, (build)-> %{projectunit} = build)
+
+    it "should have a version", ->
+      assert.ok %{projectunit}.version
+
+
+  """ else """
+  /**
+   * Project's main unit test
+   *
+   * Copyright (C) %{year} %{username}
+   */
+  var Lovely = require('lovely');
+  var Test   = Lovely.Test;
+  var assert = Lovely.assert;
+
+  describe("%{projectunit}", function() {
+    var %{projectunit} = null;
+
+    before(Test.load(module, function(build) {
+      %{projectunit} = build;
+    }));
+
+    it("should have a version number", function() {
+      assert.ok(%{projectunit}.version);
+    });
+  });
+
+  """
+
+  print " - test/#{filename}"
+  fs.writeFileSync("#{directory}/test/#{filename}", patch_source(source))
+
   print " - build/"
   fs.mkdirSync("#{directory}/build", 0o0755)


make helper functions global

make helper functions global

--- client/neuropils.coffee
+++ client/neuropils.coffee
@@ -80,7 +80,7 @@

   result

-fill_from = (selector, template, neuropil_type, result) ->
+@neuropil_fill_from = (selector, template, neuropil_type, result) ->
   for node in template.findAll(selector)
     console.log node
     if node.checked
@@ -91,7 +91,7 @@
   console.log "new result",result
   return

-dict2arr = (neuropils) ->
+@neuropil_dict2arr = (neuropils) ->
   result = []
   for _id, tarr of neuropils
     result.push( {"_id":_id, "type":tarr} )
@@ -101,10 +101,10 @@
   my_id = Session.get("modal_info").body_template_data.my_id

   neuropils = {}
-  fill_from(".neuropils-unspecified",template,"unspecified",neuropils)
-  fill_from(".neuropils-output",template,"output",neuropils)
-  fill_from(".neuropils-input",template,"input",neuropils)
-  neuropils = dict2arr(neuropils)
+  neuropil_fill_from(".neuropils-unspecified",template,"unspecified",neuropils)
+  neuropil_fill_from(".neuropils-output",template,"output",neuropils)
+  neuropil_fill_from(".neuropils-input",template,"input",neuropils)
+  neuropils = neuropil_dict2arr(neuropils)

   coll_name = Session.get("modal_info").body_template_data.collection_name
   collection = window.get_collection_from_name(coll_name)


rm log statement

rm log statement

--- server/methods.coffee
+++ server/methods.coffee
@@ -86,7 +86,6 @@
       if dbArticle.enhancements.processingStartedAt
         # If the processing started less than 100 seconds ago do not resubmit
         # the aritcle.
-        console.log 't', new Date() - dbArticle.enhancements.processingStartedAt
         if (new Date() - dbArticle.enhancements.processingStartedAt) < 100000
           return dbArticle.enhancements
       else


switched to params object for update subscription

--- server/methods.coffee
+++ server/methods.coffee
@@ -21,7 +21,7 @@
   #
   # Update stripe subscription for user with provided plan and quantitiy
   #
-  updateSubscription: (userId, plan, quantity) ->
+  updateSubscription: (userId, params) ->
     console.log 'Updating subscription for', userId
     user = BillingUser.first(_id: userId)
     if user then customerId = user.profile.customerId
@@ -31,7 +31,7 @@
     Stripe = StripeAPI(Billing.settings.secretKey)
     updateSubscription = Async.wrap Stripe.customers, 'updateSubscription'
     try
-      subscription = updateSubscription customerId, plan: plan, prorate: false, quantity: quantity
+      subscription = updateSubscription customerId, params
       Meteor.users.update _id: userId,
         $set: 'profile.subscriptionId': subscription.id
     catch e


Moved pie chart labels

Moved pie chart labels

--- app/assets/javascripts/backbone/views/meals/report_charts_view.coffee
+++ app/assets/javascripts/backbone/views/meals/report_charts_view.coffee
@@ -78,8 +78,9 @@

   pieChart: ->
     nv.models.pieChart()
-      .margin({top: 0, right: 0, bottom: 0, left: 0})
+      .margin({top: 10, right: 0, bottom: 10, left: 0})
       .showTooltipPercent(true)
+      .labelsOutside(true)
       .x((d) -> d.key)
       .y((d) -> d.y)
       .valueFormat(d3.format(',.1f'))


disable drag/resize after mark posted

disable drag/resize after mark posted

--- app/assets/javascripts/components/mark/tools/text-row-tool/index.cjsx
+++ app/assets/javascripts/components/mark/tools/text-row-tool/index.cjsx
@@ -25,6 +25,7 @@
     # set state
     mark: mark
     buttonDisabled: false
+    lockTool: false

   componentWillReceiveProps: ->
     mark = @props.mark
@@ -37,6 +38,7 @@
       # , => @forceUpdate()

   handleDrag: (e) ->
+    return if @state.lockTool
     { ex,ey } = @props.getEventOffset e
     mark = @state.mark
     markHeight = mark.yLower - mark.yUpper
@@ -90,6 +92,7 @@
     mark = @state.mark
     switch mark.status
       when 'mark'
+        @lockTool()
         @submitMark()
         mark.status = 'mark-finished'
       when 'mark-finished'
@@ -99,6 +102,14 @@
       when 'transcribe-complete'
         console.log 'NOTHING LEFT TO DO FOR THIS MARK'
     @setState mark: mark
+
+  lockTool: ->
+    @setState lockTool: true
+      , => @forceUpdate()
+
+  unlockTool: ->
+    @setState lockTool: false
+      , => @forceUpdate()

   enableButton: ->
     console.log 'enableButton() '
@@ -143,6 +154,8 @@

   render: ->
     classString = 'textRow drawing-tool'
+    if @state.lockTool then classString += ' locked'
+
     markHeight = @state.mark.yLower - @state.mark.yUpper

     strokeWidth = '6'
@@ -179,7 +192,7 @@
       />

-      { if @props.isSelected
+      { if @state.mark.status is 'mark'
           <g>
             <ResizeButton
               viewBox={"0 0 @props.imageWidth @props.imageHeight"}


[fix] Fixed dragging a row of milestones (8m)

--- app/assets/javascripts/houston/roadmap/views/edit_roadmap_view.coffee
+++ app/assets/javascripts/houston/roadmap/views/edit_roadmap_view.coffee
@@ -106,10 +106,9 @@

         return unless view.drag
         return unless view.drag.band is view.drag.bandOver
-        return unless view.drag.maxLeft
-        return unless ui.position.left > view.drag.maxLeft

         delta = ui.position.left - view.drag.offsetLeft + view.drag.originalLeft - view.drag.maxLeft
+        return if delta <= 0

         for i in [0...view.drag.milestonesAfter.length]
           milestone = view.drag.milestonesAfter[i]


select2 fix

select2 fix

--- app/assets/javascripts/rademade_admin/app/common/relation-list.coffee
+++ app/assets/javascripts/rademade_admin/app/common/relation-list.coffee
@@ -30,6 +30,7 @@
     $select = $(this)
     $table = $select.siblings('.select2-items-list')
     addTable($select) if $table.length is 0
+    $table.html('') unless isMultiple
     addItem(e.added, $table)
     hideTags()
   $item.select2('enable', false) if $item.val().length > 0


fix post

fix post

--- core/coffee/dropboard.coffee
+++ core/coffee/dropboard.coffee
@@ -51,6 +51,7 @@
   initApp : () =>
     app = express();
     app.use bodyParser.json()
+    app.use bodyParser.urlencoded()
     app.use partials()

     ### Template Setting ###


Escape description

Escape description

--- framer/Components/ShareComponent.coffee
+++ framer/Components/ShareComponent.coffee
@@ -360,13 +360,16 @@
 			urlRegex = /[-a-zA-Z0-9@:%_\+.~#?&//=]{2,256}\.[a-z]{2,4}\b(\/[-a-zA-Z0-9@:%_\+.~#?&//=]*)?/gi
 			httpRegex = /^((http|https):\/\/)/
 			lineBreakRegex = /(?:\r\n|\r|\n)/g
+			removeAllTagsExceptBreaks = /<(?!br\s*\/?)[^>]+>/g
+
+			text = _.escape(text)
+			text = _.trimEnd(text)
+			text = text.replace(lineBreakRegex, "
")

 			urlified = text.replace urlRegex, (url) ->
 				href = url
 				href = "//#{url}" if not httpRegex.test(url)
 				return "#{url}"
-
-			urlified.replace(lineBreakRegex, "
")

 		@description = new ShareLayer
 			parent: @info