- fixes issue #24: reworked overall options handling

--- src/coffeeCoverage.coffee
+++ src/coffeeCoverage.coffee
@@ -88,29 +88,28 @@
 # e.g. `coffeeCoverage.register {path: 'abbr', basePath: "#{__dirname}/.." }`
 #
 exports.register = (options) ->
+
+    if options.basePath
+        options.basePath = path.resolve options.basePath
+
+    if options.initAll
+        # Recursively instrument everything in the base path to generate intialization data.
+        options.initFileStream = new StringStream()
+
     coverage = new exports.CoverageInstrumentor options
     module = require('module');

-    if options.basePath
-        basePath = path.resolve options.basePath
-
-        if options.initAll
-            # Recursively instrument everything in the base path to generate intialization data.
-            initStream = new StringStream()
-            coverage.instrumentDirectory options.basePath, null, {
-                exclude: options.exclude
-                recursive: true
-                initFileStream: initStream
-            }
-            eval initStream.data
+    if options.basePath and options.initAll
+        coverage.instrumentDirectory options.basePath, null
+        eval options.initFileStream.data

     # Return true if we should exclude a file
     excludeFile = (fileName) ->
         exclude = options.exclude or []

         excluded = false
-        if basePath
-            relativeFilename = getRelativeFilename basePath, fileName
+        if options.basePath
+            relativeFilename = getRelativeFilename options.basePath, fileName
             if relativeFilename == fileName
                 # Only instrument files that are inside the project.
                 excluded = true
@@ -138,8 +137,8 @@

     instrumentFile = (fileName) ->
         content = fs.readFileSync fileName, 'utf8'
-        coverageFileName = getRelativeFilename basePath, fileName
-        instrumented = coverage.instrumentCoffee coverageFileName, content, options
+        coverageFileName = getRelativeFilename options.basePath, fileName
+        instrumented = coverage.instrumentCoffee coverageFileName, content
         return instrumented.init + instrumented.js

     replaceHandler = (extension) ->
@@ -163,7 +162,7 @@
                 if excludeFile fileName
                     return origStreamineCoffeeHandler.call this, module, fileName

-                compiled = instrumentFile(fileName)
+                compiled = instrumentFile fileName
                 # TODO: Pass a sourcemap here?
                 streamline_js module, fileName, compiled, null

@@ -235,13 +234,14 @@
     # a file or directory respectively.
     #
     # Throws CoverageError if there is a problem with the `source` or `out` parameters.
-    instrument: (source, out, options) ->
+    instrument: (source, out, options = {}) ->
         validateSrcDest source, out
         sourceStat = statFile source
+        @options = defaults options, defaultOptions
         if sourceStat.isFile()
-            return @instrumentFile source, out, options
+            return @instrumentFile source, out
         else if sourceStat.isDirectory()
-            return @instrumentDirectory source, out, options
+            return @instrumentDirectory source, out
         else
             throw new CoverageError("Can't instrument #{source}.")

@@ -291,22 +291,19 @@
     # Returns an object consisting of:
     #  - `lines` - the total number of instrumented lines.
     #
-    instrumentDirectory: (sourceDirectory, outDirectory, options = {}) ->
+    instrumentDirectory: (sourceDirectory, outDirectory) ->
         # Turn the source directory into an absolute path
         sourceDirectory = path.resolve sourceDirectory

         @emit "instrumentingDirectory", sourceDirectory, outDirectory

-        options = Object.create options
-        options.usedFileNames = options.usedFileNames || []
-        options.basePath = if options.basePath
-            path.resolve options.basePath
+        @options.usedFileNames = @options.usedFileNames || []
+        @options.basePath = if @options.basePath
+            path.resolve @options.basePath
         else
             sourceDirectory

         answer = {lines: 0}
-
-        options = defaults options, @options

         validateSrcDest sourceDirectory, outDirectory

@@ -327,12 +324,12 @@
         # Instrument every file in the directory
         for file in fs.readdirSync(sourceDirectory)
             skip = false
-            if file in options.exclude
+            if file in @options.exclude
                 skip = true

             sourceFile = sourceDirectory + file
-            relativePath = getRelativeFilename options.basePath, sourceFile
-            if relativePath != sourceFile then for exclude in options.exclude
+            relativePath = getRelativeFilename @options.basePath, sourceFile
+            if relativePath != sourceFile then for exclude in @options.exclude
                 if startsWith relativePath, exclude
                     skip = true

@@ -344,8 +341,8 @@

             sourceStat = statFile sourceFile

-            if options.recursive and sourceStat.isDirectory()
-                inst = @instrumentDirectory sourceFile, outFile, options
+            if @options.recursive and sourceStat.isDirectory()
+                inst = @instrumentDirectory sourceFile, outFile
                 answer.lines += inst.lines
             else
                 processed = false
@@ -361,9 +358,7 @@

                         # Replace the ".(lit)coffee(.md)" extension with a ".js" extension
                         outFile = @getOutputFileName outFile
-                        instrumentOptions = Object.create options
-                        instrumentOptions.fileName = relativePath
-                        inst = @instrumentFile sourceFile, outFile, instrumentOptions
+                        inst = @instrumentFile sourceFile, outFile, relativePath
                         answer.lines += inst.lines
                         processed = true
                         break
@@ -384,13 +379,13 @@
     # For other options, see `@instrumentCoffee`.
     #
     # Throws CoverageError if there is a problem with the `sourceFile` or `outFile` parameters.
-    instrumentFile: (sourceFile, outFile=null, options={}) ->
+    instrumentFile: (sourceFile, outFile=null, fileName) ->
         @emit "instrumentingFile", sourceFile, outFile

         validateSrcDest sourceFile, outFile

         data = fs.readFileSync sourceFile, 'utf8'
-        answer = @instrumentCoffee (options.fileName or sourceFile), data, options
+        answer = @instrumentCoffee (fileName or sourceFile), data

         if outFile
             writeToFile outFile, (answer.init + answer.js)
@@ -435,20 +430,20 @@
     # * `js` - the compiled JavaScript, instrumented to collect coverage data.
     # * `lines` - the total number of instrumented lines.
     #
-    instrumentCoffee: (fileName, fileData, options = {}) ->
+    instrumentCoffee: (fileName, fileData) ->
         origFileName = fileName
         literate = /\.(litcoffee|coffee\.md)$/.test(fileName)

-        switch options.path
+        switch @options.path
             when 'relative' then fileName = stripLeadingDotOrSlash fileName
             when 'abbr' then fileName = abbreviatedPath stripLeadingDotOrSlash fileName
             else fileName = path.basename fileName

         # Generate a unique fileName if required.
-        if options.usedfileNames
-            if fileName in options.usedfileNames
-                fileName = generateUniqueName options.usedfileNames, fileName
-            options.usedfileNames.push fileName
+        if @options.usedfileNames
+            if fileName in @options.usedfileNames
+                fileName = generateUniqueName @options.usedfileNames, fileName
+            @options.usedfileNames.push fileName

         quotedFileName = toQuotedString fileName

@@ -463,6 +458,7 @@
             debug "Examining  l:#{node.locationData.first_line + 1} d:#{depth} #{nodeType(node)}"

             if (nodeType(node) != "Block") or node.coffeeCoverageDoNotInstrument
+
                 if nodeType(node) is "If" and node.isChain
                     # Chaining is where coffee compiles something into `... else if ...`
                     # instead of '... else {if ...}`.  Chaining produces nicer looking coder
@@ -558,11 +554,11 @@

         # Compile the instrumented CoffeeScript and write it to the JS file.
         try
-            js = ast.compile {bare: options.bare, literate: literate}
+            js = ast.compile {bare: @options.bare, literate: literate}
         catch err
             throw new CoverageError("Could not compile #{fileName} after annotating: #{err.stack}")

-        options.initFileStream?.write init
+        @options.initFileStream?.write init

         answer = {
             init: init


Accept image

Accept image

--- src/slack.coffee
+++ src/slack.coffee
@@ -15,6 +15,7 @@
 textify = (attachment) ->
   pretext = attachment?.pretext
   text = attachment?.text
+  image_url = attachment?.image_url
   fields = attachment?.fields?.map (field) ->
     rendered = "  #{field.title}"
     if field.value


Add a test that clicks on the digger when expired

--- test/integration/free_trial.coffee
+++ test/integration/free_trial.coffee
@@ -58,6 +58,14 @@
           url.should.equal "#{base_url}/pricing/expired"
           done()

+    context 'when I click the super duper ScraperWiki digger', ->
+      before (done) ->
+        wd40.click '#logo', done
+
+      it 'should still be on the pricing expired page', (done) ->
+        wd40.trueURL (err, url) ->
+          url.should.equal "#{base_url}/pricing/expired"
+          done()

 describe 'Paid user', ->


Renaming variables per suggestion

Renaming variables per suggestion

--- test/unit/dredd-events-test.coffee
+++ test/unit/dredd-events-test.coffee
@@ -15,12 +15,12 @@
         assert.ok callback.notCalled
         done()

-    it 'should callback if registered', (done) ->
-      dreddEvents.beforeAll (d) ->
-        assert.ok(typeof d is 'function')
-        d()
-      dreddEvents.runBeforeAll (d) ->
-        done()
+    it 'should callback if registered', (testDone) ->
+      dreddEvents.beforeAll (initDone) ->
+        assert.ok(typeof initDone is 'function')
+        initDone()
+      dreddEvents.runBeforeAll (runDone) ->
+        testDone()

   describe 'after', () ->
     callback = sinon.spy()
@@ -30,9 +30,9 @@
         assert.ok callback.notCalled
         done()

-    it 'should callback if registered', (done) ->
-      dreddEvents.afterAll (d) ->
-        assert.ok(typeof d is 'function')
-        d()
-      dreddEvents.runAfterAll (d) ->
-        done()
+    it 'should callback if registered', (testDone) ->
+      dreddEvents.afterAll (initDone) ->
+        assert.ok(typeof initDone is 'function')
+        initDone()
+      dreddEvents.runAfterAll (runDone) ->
+        testDone()


fix bug in Each Component: getContentComponent should always invalidate all children if items is Function

totoMVC now works correctly, include switching between active and complete

--- src/core/base/Each.coffee
+++ src/core/base/Each.coffee
@@ -18,6 +18,7 @@
     me = this

     if typeof items == 'function'
+      @isFunction = true
       !items.invalidate and items = renew(items)
       items.onInvalidate @invalidateTransform.bind(@)

@@ -47,44 +48,44 @@

   getItems: ->

-    {items} = @
-    if typeof items == 'function'
-      isFunction = true
+  getContentComponent: ->
+    {listComponent, items, isFunction, needSort} = @
+
+    if isFunction
       items = items()
       if !items or typeof(items)!='object' then throw new Error 'Each Component need an array or object'

-    if @needSort
+    if !(@isArrayItems = items instanceof Array)
+      items = for key, value of items then [key, value]
+
+    if needSort
       items.sort(@sortFunction)
-      @_items = items
-      return

-    _items = @_items
-    _items and _items.watchingComponents and delete _items.watchingComponents[@dcid]
-    watchingMe = items and items.watchingComponents and items.watchingComponents[@dcid]
-    if items not instanceof Array
-      @isArrayItems = false
-      if !@notWatch and !watchingMe then watchEachObject items, @
-      items = for key, value of items then [key, value]
     else
-      if !@notWatch and !watchingMe  then watchEachList items, @
-      @isArrayItems = true
+      _items = @_items
+      _items and _items.watchingComponents and delete _items.watchingComponents[@dcid]
+      watchingMe = items and items.watchingComponents and items.watchingComponents[@dcid]
+      if !@notWatch and !watchingMe
+        if @isArrayItems then watchEachList items, @
+        else watchEachObject items, @
+
     @_items = items

-  getContentComponent: ->
-    me = @
-    {listComponent} = @
-    @getItems()
-    length = @_items.length
+    length = items.length
     if length<listComponent.children.length
       @_setLength(length)
-    else length and @invalidateChildren(0, length)
+      if isFunction or needSort or !@isArrayItems
+        @invalidateChildren(0, length)
+
+    else @invalidateChildren(0, length)
+
     listComponent

-  getChild: (i) ->
+  getChild: (index) ->
     me = @

     if keyFunction
-      memoKey = if @isArrayItems then keyFunction(_items[i], i) else keyFunction(_items[i][0], _items[i][1], i)
+      memoKey = if @isArrayItems then keyFunction(_items[index], index) else keyFunction(_items[index][0], _items[index][1], index)

     {listComponent, cacheChildren, children, childReactives, keyFunction, itemFn} = @
     children = listComponent.children
@@ -96,37 +97,37 @@
       if child=@memoComponents[memoKey]
         child.valid = false
         child.transformValid = false
-        children[i] = cacheChildren[i] = child
+        children[index] = cacheChildren[index] = child
         @memoChildMap[memoKey] = child
         return child

-    if i<children.length
-      child = children[i]
+    if index<children.length
+      child = children[index]
       child.valid = false
       child.transformValid = false
-    else if i<cacheChildren.length
-      child = children[i] = cacheChildren[i]
+    else if index<cacheChildren.length
+      child = children[index] = cacheChildren[index]
       child.valid = false
       child.transformValid = false
     else
-        childReactives[i] = react ->
+        childReactives[index] = react ->
           items = me._items
-          item = items[i]
+          item = items[index]

           # if item.pouring, always invalidate child component
-          # so do not move this line after "... child = new Func childReactives[i]"
+          # so do not move this line after "... child = new Func childReactives[index]"
           if itemFn.pouring
             child.invalidateTransform()

           result =
-            if me.isArrayItems then itemFn(item, i, items, me)
+            if me.isArrayItems then itemFn(item, index, items, me)
             else
               [key, value] = item
               itemFn(value, key, index, items, me)

-        children[i] = cacheChildren[i] = child = new Func childReactives[i]
+        children[index] = cacheChildren[index] = child = new Func childReactives[index]
         child.holder = listComponent
-        listComponent.dcidIndexMap[child.dcid] = i
+        listComponent.dcidIndexMap[child.dcid] = index

     child


Tests: Added commentWithCode test.

Tests: Added commentWithCode test.

--- tests/src/activity/activity.coffee
+++ tests/src/activity/activity.coffee
@@ -278,3 +278,22 @@

     browser.end()

+
+  commentWithCode: (browser) ->
+
+    helpers.beginTest(browser)
+
+    post      = helpers.getFakeText()
+    timestamp = Date.now()
+    code      = "console.log(#{timestamp})"
+    comment   = '```' + code + '```'
+    selector  = '[testpath=ActivityListItemView]:first-child .comment-contents .has-markdown code'
+
+    helpers.doPostActivity(browser, post)
+    helpers.doPostComment(browser, comment, no)
+
+    browser
+      .pause 2000
+      .assert.containsText selector, code # Assertion
+      .end()
+


Tests: Added cancelCommentDeletion test.

--- tests/src/activity/activity.coffee
+++ tests/src/activity/activity.coffee
@@ -131,3 +131,23 @@
       .end()

+  cancelCommentDeletion: (browser) ->
+
+    comment         = helpers.postComment(browser)
+
+    selector        = '[testpath=activity-list] section:nth-of-type(1) [testpath=ActivityListItemView]:first-child'
+    commentSelector = selector + ' .comment-container button.comment-menu'
+
+    browser
+      .waitForElementPresent    commentSelector, 5000
+      .click                    commentSelector
+      .waitForElementVisible    '.kdcontextmenu .delete-comment', 5000
+      .click                    '.kdcontextmenu .delete-comment'
+      .click                    '.kdmodal-inner .modal-cancel'
+      .waitForElementNotPresent '.kdoverlay', 5000
+
+    browser
+      .assert.containsText selector + ' .comment-container', comment # Assertion
+      .end()
+
+


Tests: Added deletePost test.

Tests: Added deletePost test.

--- tests/src/activity/activity.coffee
+++ tests/src/activity/activity.coffee
@@ -1,6 +1,7 @@
 utils   = require '../utils/utils.js'
 helpers = require '../helpers/helpers.js'
 faker   = require 'faker'
+assert  = require 'assert'

 module.exports =
@@ -52,3 +53,22 @@
       .end()

+  deletePost: (browser) ->
+
+    helpers.postActivity(browser)
+    helpers.postActivity(browser, no)
+
+    post        =  faker.Lorem.paragraph().replace(/(?:\r\n|\r|\n)/g, '')
+    selector    = '[testpath=activity-list] section:nth-of-type(1) [testpath=ActivityListItemView]:first-child'
+
+    browser
+      .waitForElementVisible        selector + ' .settings-menu-wrapper', 10000
+      .click                        selector + ' .settings-menu-wrapper'
+      .click                        '.kdcontextmenu .delete-post'
+      .click                        '.kdmodal-inner .modal-clean-red'
+      .pause                        3000, ->
+        text = browser.getText selector
+        assert.notEqual text, post # Assertion
+      .end()
+
+


Bug fix

Bug fix

--- memory_sync.coffee
+++ memory_sync.coffee
@@ -11,10 +11,9 @@
     @store = {}

     # publish methods and sync on model
-    @url = _.result(@model_type.prototype, 'url')
-    @model_type.model_name = Utils.parseUrl(@url).model_name unless @model_type.model_name # model_name can be manually set
-    throw new Error('Missing model_name for model') unless @model_type.model_name
-    @model_type.prototype.urlRoot = "/#{inflection.underscore(@model_type.model_name)}" unless @url # add a default url
+    unless @model_type.model_name # model_name will come from the url
+      @url = _.result(@model_type.prototype, 'url')
+      @model_type.model_name = Utils.parseUrl(@url).model_name
     @model_type._sync = @
     @model_type._schema = new Schema(@model_type)


Improved comments on miniLockLib.getKeyPair(...) and others.

--- miniLockLib.coffee
+++ miniLockLib.coffee
@@ -55,16 +55,12 @@
 # acts as the key derivation salt to ensure your key pair is unique just in
 # case someone else uses the same secret phrase as you do.
 #
-# Call `miniLockLib.getKeyPair` to calculate a key pair from a secret phrase
-# and email address.
-# Why are both inputs decoded?
-# Why is the secret phrase hashed?
-#
-# Your `callback` receives a pair of `keys` like this:
+# Call `miniLockLib.getKeyPair` to generate a set of keys from a secret phrase
+# and email address. Your `callback` receives a pair of `keys` like this:
 #
 #     miniLockLib.getKeyPair secretPhrase, emailAddress, (keys) ->
-#        keys.publicKey is [32-bit Uint8Array]
-#        keys.secretKey is [32-bit Uint8Array]
+#        keys.publicKey is a Uint8Array
+#        keys.secretKey is a Uint8Array
 #
 miniLockLib.getKeyPair = (secretPhrase, emailAddress, callback) ->
   # Decode each input into a Uint8Array of bytes.
@@ -107,10 +103,9 @@
 # miniLock IDs are meant to be easily communicable via email or instant messaging.
 # A miniLock ID is a Base58 encoded string of a list of 33 numbers.
 # The first 32 numbers of your ID correspond to your curve25519 public key.
-# The last number is a checksum that was is derived by hashing your public key with BLAKE2
-# with a 1-byte output.
-# After constructing the 33 bytes of the miniLock ID, it is encoded
-# into a Base58 representation.
+# The last number is a checksum that was is derived by hashing your public key
+# with BLAKE2  with a 1-byte output. After constructing the 33 bytes of the
+# miniLock ID, it is encoded into a Base58 representation.
 #
 miniLockLib.makeID = (publicKey) ->
   if publicKey?.length is 32
@@ -222,6 +217,7 @@
 CryptoWorker = ->
   new Worker(miniLockLib.pathToScripts+'/miniLockCryptoWorker.js')

+# Explanations of miniLock’s numeric error codes.
 CryptoWorker.ErrorMessages =
   1: 'miniLock could not encrypt this file.'
   2: 'miniLock could not decrypt this file.'
@@ -232,7 +228,7 @@
   7: 'The integrity of this file could not be verified.'

-# Construct a BLAKE2 hash digest of `input`. Specify the digest `length` as a `Number`.
+# Construct a BLAKE2 hash digest of `input`. Specify digest `length` as a `Number`.
 BLAKE2HashDigest = (input, options={}) ->
   hash = new BLAKE2s(options.length)
   hash.update(input)


Implemented Serializer, getting the jsonb error

--- lib/Serializer.coffee
+++ lib/Serializer.coffee
@@ -1,5 +1,6 @@
 _ = require "underscore"
 Match = require "mtr-match"
+moment = require "moment"

 class Serializer
   constructor: (options) ->
@@ -18,15 +19,15 @@
   toExternal: (internalObject) -> @transform(internalObject, "toExternal")

   # an external object identifier field may be called "id", which conflicts with our PostgreSQL "id" field
-  keymap: -> throw "Implement me! At the very least, map the external object identifier field and internal object _uid field"
+  keymap: -> throw new Error("Implement me! At the very least, map the external object identifier field and internal object _uid field")

   forJSONResponse: ->
     transformers = {}
     for column in @model.getColumns()
       if column.getColumnType() is "timestamptz"
         transformers[column.getColumnName()] =
-          toInternal: @toDate
-          toExternal: @fromDate
+          toInternal: @toDate.bind(@)
+          toExternal: @fromDate.bind(@)
     transformers

   transform: (source, direction) ->
@@ -40,7 +41,8 @@

   dereference: (keymap, name) -> keymap[name] or name

-  toDate: (value) -> new Date(value)
-  fromDate: (value) -> value.toISOString()
+  toDate: (value) -> moment(value, @dateFormat()).toDate()
+  fromDate: (value) -> moment(value).format(@dateFormat())
+  dateFormat: -> throw new Error("Implement me!")

 module.exports = Serializer


Add private method docs to DisplayBuffer::outermostFoldsInBufferRange

--- src/display-buffer.coffee
+++ src/display-buffer.coffee
@@ -215,6 +215,8 @@
   largestFoldContainingBufferRow: (bufferRow) ->
     @foldsContainingBufferRow(bufferRow)[0]

+  # Returns the folds in the given row range (exclusive of end row) that are
+  # not contained by any other folds.
   outermostFoldsInBufferRowRange: (startRow, endRow) ->
     @findFoldMarkers(containedInRange: [[startRow, 0], [endRow, 0]])
       .map (marker) => @foldForMarker(marker)


remove unused method

remove unused method

--- src/flux/task-plan.coffee
+++ src/flux/task-plan.coffee
@@ -171,12 +171,6 @@
   setDefaultTimesForCourse: (id, courseId, periods) ->
     tasking_plans = @_getDefaultTaskingTimes(id, courseId, periods)
     @_change(id, {tasking_plans})
-
-  # set taskings that lack an opens_at to the default
-  setBlankOpensAtDateForCourse: (id, courseId, opensAtDate) ->
-    opensAt = "#{opensAtDate} #{CourseStore.get(courseId)?.default_open_time}"
-    _.each @_getPlan(id)?.tasking_plans, (tasking) ->
-      tasking.opens_at ?= opensAt

   setPeriods: (id, courseId, periods, isDefault = false, useCourseDefault = true) ->
     plan = @_getPlan(id)


up a bit

up a bit

--- models/team.coffee
+++ models/team.coffee
@@ -41,7 +41,7 @@
 TeamSchema.static 'canRegister', (next) ->
   Team.count null, (err, count) ->
     return next err if err
-    max = 302 + 1 # +1 because team fortnight labs doesn't count
+    max = 305 + 1 # +1 because team fortnight labs doesn't count
     next null, count < max, max - count

 TeamSchema.static 'uniqueName', (name, next) ->


:lipstick: Cleanup.

:lipstick: Cleanup.

--- lib/jumpy-view.coffee
+++ lib/jumpy-view.coffee
@@ -58,6 +58,8 @@
         @statusBarJumpy = document.getElementById 'status-bar-jumpy'

     getKey: (character) ->
+        @statusBarJumpy?.classList.remove 'no-match'
+
         isMatchOfCurrentLabels = (character, labelPosition) ->
             found = false
             atom.workspace.observeTextEditors (editor) ->
@@ -74,8 +76,6 @@
             @statusBarJumpy?.classList.add 'no-match'
             @statusBarJumpyStatus?.innerHTML = 'No match!'
             return
-        else # TODO: Test removing this and shifting back next 2 lines?
-            @statusBarJumpy?.classList.remove 'no-match'

         if not @firstChar
             @firstChar = character


Reorganized Prize cards, and more importantly gave them all startingSupply -> 0.

--- cards.coffee
+++ cards.coffee
@@ -477,6 +477,67 @@
       state.gainCard(state.current, newCard, 'hand')
 }

+# Prize cards
+# -----------
+# Because Prize cards can only be gained through Tournament, and all have
+# cost = 0, startingSupply -> 0, and mayBeBought -> false it is useful to
+# have a prototype prize. The prototype has isAction: true since 4 of the 5
+# prizes are action cards.
+
+prize = makeCard 'prize', basicCard, {
+  cost: 0
+  isPrize: true
+  isAction: true
+  mayBeBought: (state) -> false
+  startingSupply: (state) -> 0
+}, true
+
+makeCard 'Bag of Gold', prize, {
+  actions: +1
+  playEffect: (state) ->
+    state.gainCard(state.current, c.Gold, 'draw')
+    state.log("...putting the Gold on top of the deck.")
+}
+
+makeCard 'Diadem', prize, {
+  isAction: false
+  isTreasure: true
+  getCoins: (state) -> 2 + state.current.actions
+}
+
+makeCard 'Followers', prize, {
+  cards: +2
+  isAttack: true
+  playEffect: (state) ->
+    state.gainCard(state.current, c.Estate)
+    state.attackOpponents (opp) ->
+      state.gainCard(opp, c.Curse)
+      if opp.hand.length > 3
+        state.requireDiscard(opp, opp.hand.length - 3)
+}
+
+# Since there is only one Princess card, and Princess's cost
+# reduction effect has the clause "while this is in play",
+# state.princesses will never need to be greater than 1.
+makeCard 'Princess', prize, {
+  buys: 1
+  playEffect:
+    (state) ->
+      state.princesses = 1
+}
+
+makeCard 'Trusty Steed', prize, {
+  playEffect: (state) ->
+    benefit = state.current.ai.choose('benefit', state, [
+      {cards: 2, actions: 2},
+      {cards: 2, coins: 2},
+      {actions: 2, coins: 2},
+      {cards: 2, horseEffect: yes},
+      {actions: 2, horseEffect: yes},
+      {coins: 2, horseEffect: yes}
+    ])
+    applyBenefit(state, benefit)
+}

 # Miscellaneous cards
 # -------------------
@@ -577,17 +638,6 @@
       state.current.setAside = []
 }

-makeCard 'Bag of Gold', action, {
-  cost: 0
-  actions: +1
-  isPrize: true
-  mayBeBought: (state) -> false
-
-  playEffect: (state) ->
-    state.gainCard(state.current, c.Gold, 'draw')
-    state.log("...putting the Gold on top of the deck.")
-}
-
 makeCard 'Bank', c.Silver, {
   cost: 7
   getCoins: (state) ->
@@ -739,13 +789,6 @@
         else
           state.log("#{opp.ai} has no Copper in hand.")
           state.revealHand(opp)
-}
-
-makeCard 'Diadem', c.Silver, {
-  cost: 0
-  isPrize: true
-  mayBeBought: (state) -> false
-  getCoins: (state) -> 2 + state.current.actions
 }

 makeCard "Duke", c.Estate, {
@@ -805,21 +848,6 @@
   playEffect: (state) ->
     state.attackOpponents (opp) ->
       state.gainCard(opp, c.Curse)
-}
-
-makeCard "Followers", action, {
-  cost: 0
-  cards: +2
-
-  isAttack: true
-  isPrize: true
-  mayBeBought: (state) -> false
-  playEffect: (state) ->
-    state.gainCard(state.current, c.Estate)
-    state.attackOpponents (opp) ->
-      state.gainCard(opp, c.Curse)
-      if opp.hand.length > 3
-        state.requireDiscard(opp, opp.hand.length - 3)
 }

 makeCard "Gardens", c.Estate, {
@@ -1235,20 +1263,6 @@
       if attackSuccess
         state.current.mats.pirateShip += 1
         state.log("...#{state.current.ai} takes a Coin token (#{state.current.mats.pirateShip} on the mat).")
-}
-
-makeCard 'Princess', action, {
-  cost: 0
-  buys: 1
-  isPrize: true
-  mayBeBought: (state) -> false
-
-  # Since there is only one Princess and its cost reduction effect has
-  # the "while this is in play" clause, setting state.princesses to 1
-  # works.
-  playEffect:
-    (state) ->
-      state.princesses = 1
 }

 makeCard 'Quarry', c.Silver, {


completed language reactivity and added getLanguages

--- t9n.coffee
+++ t9n.coffee
@@ -55,8 +55,12 @@
     @depLanguage.depend()
     return @language

+  @getLanguages: () ->
+    @dep.depend()
+    return Object.keys(@maps).sort()
+
   @setLanguage: (language) ->
-    if(!@maps[language])
+    if(!@maps[language] || @language == language)
       return;
     @language = language
     @depLanguage.changed()


nicer icon

nicer icon

--- lib/plots/pane.coffee
+++ lib/plots/pane.coffee
@@ -26,6 +26,6 @@
     'Plots'

   getIconName: ->
-    'pencil'
+    'comment'

 PlotPane.registerViews()


Fix misnamed method

Fix misnamed method

--- lib/presenters.coffee
+++ lib/presenters.coffee
@@ -18,7 +18,7 @@
     estimate: prediction.prediction.minutes
     percentComplete: helpers.calculatePercentComplete(prediction.prediction.minutes)

-  buildTitle: ->
+  buildGeneralContext: ->
     routeNumber: @predictions[0].route.number
     routeDirection: @predictions[0].route.direction
     busNumber: @predictions[0].busNumber


notify the user if there is no result for references. fixes #188

--- lib/atom-ternjs-reference.coffee
+++ lib/atom-ternjs-reference.coffee
@@ -48,7 +48,9 @@
     @manager.client.update(editor).then (data) =>
       return if data.isQueried
       @manager.client.refs(atom.project.relativizePath(editor.getURI())[1], {line: position.row, ch: position.column}).then (data) =>
-        return unless data
+        if !data
+          atom.notifications.addInfo('No references found.', { dismissable: false })
+          return
         @references = data
         for ref in data.refs
           ref.file = ref.file.replace(/^.\//, '')


Fix removing markers in panes; keep markers on words in different panes

Fixes #178

--- lib/highlighted-area-view.coffee
+++ lib/highlighted-area-view.coffee
@@ -120,7 +120,7 @@
     editor = @getActiveEditor()
     return unless editor

-    @removeMarkers(editor.id)
+    @removeAllMarkers()

     return if @disabled
     return if editor.getLastSelection().isEmpty()
@@ -154,14 +154,15 @@

     @resultCount = 0
     if atom.config.get('highlight-selected.highlightInPanes')
+      originalEditor = editor
       @getActiveEditors().forEach (editor) =>
-        @highlightSelectionInEditor(editor, regexSearch, regexFlags)
+        @highlightSelectionInEditor(editor, regexSearch, regexFlags, originalEditor)
     else
       @highlightSelectionInEditor(editor, regexSearch, regexFlags)

     @statusBarElement?.updateCount(@resultCount)

-  highlightSelectionInEditor: (editor, regexSearch, regexFlags) ->
+  highlightSelectionInEditor: (editor, regexSearch, regexFlags, originalEditor) ->
     return unless editor?
     markerLayer = @editorToMarkerLayerMap[editor.id]['visibleMarkerLayer']
     markerLayerForHiddenMarkers = @editorToMarkerLayerMap[editor.id]['hiddenMarkerLayer']
@@ -179,7 +180,8 @@
         return unless newResult?
         @resultCount += 1

-        if @showHighlightOnSelectedWord(newResult.range, @selections)
+        if @showHighlightOnSelectedWord(newResult.range, @selections) &&
+           originalEditor?.id == editor.id
           marker = markerLayerForHiddenMarkers.markBufferRange(newResult.range)
           @emitter.emit 'did-add-selected-marker', marker
           @emitter.emit 'did-add-selected-marker-for-editor',
@@ -219,7 +221,7 @@
     outcome

   removeAllMarkers: =>
-    Object.keys @editorToMarkerLayerMap, @removeMarkers
+    Object.keys(@editorToMarkerLayerMap).forEach(@removeMarkers)

   removeMarkers: (editorId) =>
     return unless @editorToMarkerLayerMap[editorId]?


Email field was in naming conflict with Meteor e-mail package, so renamed it to StarEmail.

--- lib/star_field.coffee
+++ lib/star_field.coffee
@@ -260,7 +260,7 @@
     PasswordEditor

-class @Email extends Text
+class @StarEmail extends Text
   constructor: (options) ->
     super(options)
     @addValidator Validators.email()


Fix prefixer check

Fix prefixer check

--- lib/transition.coffee
+++ lib/transition.coffee
@@ -21,7 +21,7 @@
       prop   = param[0].value
       continue if prop[0] == '-'
       prefixer = @prefixes.add[prop]
-      continue unless prefixer
+      continue if not prefixer?.prefixes

       for prefix in prefixer.prefixes
         prefixed = @prefixes.prefixed(prop, prefix)


Fix the architecture string used when downloading qemu versions

Closes: #1019
Change-type: patch
Signed-off-by: Cameron Diver <1d572acbfa68c7c6e541c7b840d6b622e5c0dc91@balena.io>

--- lib/utils/qemu.coffee
+++ lib/utils/qemu.coffee
@@ -34,7 +34,9 @@
 		new Promise (resolve, reject) ->
 			read = fs.createReadStream(qemu)
 			write = fs.createWriteStream(binPath)
+
 			read
+			.on('error', reject)
 			.pipe(write)
 			.on('error', reject)
 			.on('finish', resolve)
@@ -77,15 +79,19 @@
 	.then (qemuPath) ->
 		new Promise (resolve, reject) ->
 			installStream = fs.createWriteStream(qemuPath)
-			downloadArchiveName = "qemu-3.0.0+resin-#{arch}.tar.gz"
+
+			qemuArch = balenaArchToQemuArch(arch)
+			downloadArchiveName = "qemu-3.0.0+resin-#{qemuArch}.tar.gz"
 			qemuUrl = "https://github.com/balena-io/qemu/releases/download/#{QEMU_VERSION}/#{downloadArchiveName}"
+
 			extract = tar.extract()
 			extract.on 'entry', (header, stream, next) ->
 				stream.on('end', next)
-				if header.name.includes("qemu-#{arch}-static")
+				if header.name.includes("qemu-#{qemuArch}-static")
 					stream.pipe(installStream)
 				else
 					stream.resume()
+
 			request(qemuUrl)
 			.on('error', reject)
 			.pipe(zlib.createGunzip())
@@ -93,6 +99,10 @@
 			.pipe(extract)
 			.on('error', reject)
 			.on 'finish', ->
-				# make qemu binary executable
 				fs.chmodSync(qemuPath, '755')
 				resolve()
+
+balenaArchToQemuArch = (arch) ->
+	switch arch
+		when 'armv7hf', 'rpi' then 'arm'
+		else arch


minor implementation change

minor implementation change

--- library/semantic/formular.coffee
+++ library/semantic/formular.coffee
@@ -75,7 +75,7 @@
         assert _.isArray(data), "got invalid data object"
         assert _.isString(id), "got invalid identification"
         assert _.isString(message), "got no fail message"
-        object = _.find data, (rec) -> rec.identity is id
+        object = try _.find data, identity: id.toString()
         assert _.isPlainObject(object), "no #{id} object"
         conditions = -> (object.checked or false) is yes
         functional = -> check.call object, object.value


ZMQKernel: set @shutdown default to no restart

--- lib/zmq-kernel.coffee
+++ lib/zmq-kernel.coffee
@@ -103,12 +103,12 @@
             atom.notifications.addWarning 'Cannot interrupt this kernel'

-    shutdown: (restart) ->
+    shutdown: (restart = false) ->
         requestId = 'shutdown_' + uuid.v4()
         message = @_createMessage 'shutdown_request', requestId

         message.content =
-            restart: not not restart
+            restart: restart

         @shellSocket.send new jmp.Message message


full fix #256

full fix #256

--- games/tutorial.coffee
+++ games/tutorial.coffee
@@ -264,6 +264,7 @@
     }
     {
       name: 'Click "Edit" to Modify; "Save" and "Run" to Share'
+      focus: 'edit'
       comment: '
       Can edit the level name, sprite name, and sprite color. (DEVELOP)
       Must first solve the puzzle to validate it. (TEST)
@@ -283,7 +284,7 @@
     }
     {
       name: 'Click "Edit" & Blue Area to Add & Select Logs'
-      editing: true
+      focus: 'edit'
       comment: '
       Can create arbitrary sprites and program them if in edit mode.
       '
@@ -302,7 +303,7 @@
     }
     {
       name: 'Click "Edit", "log", then "delete" to Remove'
-      editing: true
+      focus: 'edit'
       comment: '
       Can create arbitrary sprites and program them if in edit mode.
       '
@@ -322,6 +323,7 @@
     }
     {
       name: 'Use "Edit" to Create, Test & Share Your Level'
+      focus: 'edit'
       comment: '
       Encourage students to express themselves.
       Track the solution and use that for the new goal metrics.


Avoid stale file

Avoid stale file

--- src/npx/help.coffee
+++ src/npx/help.coffee
@@ -1,8 +1,6 @@
 ###
 No command line parameters
 ###
-
-echo "exit"

 wsh.Stderr.Writeline """
   #{PACKAGE.mingzi} v#{PACKAGE.version}: #{PACKAGE.description}


move helpers out for easier testing

move helpers out for easier testing

--- src/opensong.coffee
+++ src/opensong.coffee
@@ -38,12 +38,12 @@

   transpose: (amount) ->
     Handlebars.registerHelper 'transpose', (chord) ->
-      transposeChord chord, amount || 0
+      opensong.helper.transposeChord chord, amount || 0

     this.renderLyrics() # rerender

   setLyrics: (lyrics) ->
-    @model = parseLyrics lyrics
+    @model = opensong.helper.parseLyrics lyrics
     this.renderLyrics()

   renderLyrics: ->
@@ -63,6 +63,61 @@
       return domElem

     undefined
+
+  Handlebars.registerHelper 'human_header', (abbr) ->
+    opensong.helper.humanizeHeader abbr
+
+  Handlebars.registerHelper 'transpose', (chord) ->
+    chord # just return chord, no transposing initially
+
+
+opensong = opensong || {}
+opensong.helper = opensong.helper || {}
+
+opensong.helper.transposeChord = (chord, amount) ->
+    chords = [
+      "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B",
+      "C", "Db", "D", "Eb", "E", "F", "Gb", "G", "Ab", "A", "Bb", "B"
+    ]
+
+    outputChords = []
+    for c in chord.split "/"
+      m = /^([A-G][#b]?)(.*)$/.exec c
+      return chord unless m
+
+      [_, chordRoot, chordExt] = m
+      index = chords.indexOf chordRoot
+      if index < 0 # use chord if not found
+        outputChords.push c
+        continue
+
+      # make negative amounts work, always transpose to sharps
+      newIndex = (index + amount + chords.length) % (chords.length / 2)
+      outputChords.push chords[newIndex] + chordExt
+
+    outputChords.join "/"
+
+
+opensong.helper.humanizeHeader = (abbr) ->
+    abbArr = /([a-zA-Z]+)(\d*)/.exec(abbr)[1..]
+    char = abbArr[0]
+
+    abbArr[0] = switch char
+      when "C"
+        "Chorus"
+      when "V"
+        "Verse"
+      when "B"
+        "Bridge"
+      when "T"
+        "Tag"
+      when "P"
+        "Pre-Chorus"
+      else
+        char
+
+    abbArr.pop() unless abbArr[1]? # remove num if empty
+    abbArr.join " "

   ###

@@ -84,7 +139,7 @@
   ]

   ###
-  parseLyrics = (lyrics) ->
+opensong.helper.parseLyrics = (lyrics) ->
     lyricsLines = lyrics.split("\n")

     dataModel = []
@@ -159,54 +214,3 @@
           console?.log "no support for: #{line}"
     dataModel

-  transposeChord = (chord, amount) ->
-    chords = [
-      "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B",
-      "C", "Db", "D", "Eb", "E", "F", "Gb", "G", "Ab", "A", "Bb", "B"
-    ]
-
-    outputChords = []
-    for c in chord.split "/"
-      m = /^([A-G][#b]?)(.*)$/.exec c
-      return chord unless m
-
-      [_, chordRoot, chordExt] = m
-      index = chords.indexOf chordRoot
-      if index < 0 # use chord if not found
-        outputChords.push c
-        continue
-
-      # make negative amounts work, always transpose to sharps
-      newIndex = (index + amount + chords.length) % (chords.length / 2)
-      outputChords.push chords[newIndex] + chordExt
-
-    outputChords.join "/"
-
-  ###
-  Handlebars Helpers
-  ###
-
-  Handlebars.registerHelper 'human_header', (abbr) ->
-    abbArr = /(\w)(\d)?/.exec(abbr)[1..]
-    char = abbArr[0]
-
-    abbArr[0] = switch char
-      when "C"
-        "Chorus"
-      when "V"
-        "Verse"
-      when "B"
-        "Bridge"
-      when "T"
-        "Tag"
-      when "P"
-        "Pre-Chorus"
-      else
-        char
-
-    abbArr.pop() unless abbArr[1]? # remove num if empty
-    abbArr.join " "
-
-  Handlebars.registerHelper 'transpose', (chord) ->
-    chord
-


URLを外部から指定可能にした

URLを外部から指定可能にした

--- coffee/SocketService.coffee
+++ coffee/SocketService.coffee
@@ -1,10 +1,11 @@
-SocketService = ->
+SocketService = (url)->
   service = {}
   pendingCallbacks = {}
   currentMessageId = 0
   ws = undefined
   preConnectionRequests = []
   connected = false
+  url = url || window.location.hostname + (if location.port then ':' + location.port else '')

   init = ->
     service = {}
@@ -12,7 +13,8 @@
     currentMessageId = 0
     preConnectionRequests = []
     connected = false
-    ws = new WebSocket('ws://' + window.location.hostname + (if location.port then ':' + location.port else ''))
+    ws_url = 'ws://' + url
+    ws = new WebSocket(ws_url)

     ws.onopen = ->
       connected = true


util: unindented.

util: unindented.

As per discussion with @ali01 and @tenedor, we're going with this
approach. Note that it does leak util when the build system does
not introduce a closure.

--- coffee/src/util/util.coffee
+++ coffee/src/util/util.coffee
@@ -3,171 +3,171 @@
 goog.require 'acorn.config'

-acorn.util =
-
-  assert: (condition, description) ->
-    throw new Error description if not condition
-
-  urlRegEx: (url) ->
+util = acorn.util
+
+util.assert = (condition, description) ->
+  throw new Error description if not condition
+
+util.urlRegEx = (url) ->
     ///^(https?:\/\/)?#{url ? '.*'}$///

-  isUrl: (url) ->
-    url = String(url)
-    @urlRegEx().test url
-
-  isPath: (path) ->
-    /^[A-Za-z\/.-_]+$/.test path
-
-  # helpers to construct acorn urls TODO: delete these?
-  url: ->
-    path = _.toArray(arguments).join '/'
-    "http://#{acorn.config.domain}/#{path}"
-
-  apiUrl: ->
-    apiPath = "api/v#{acorn.config.api.version}".split '/'
-    @url.apply(@, apiPath.concat _.toArray arguments)
-
-  imgUrl: ->
-    @url.apply(@, ['img'].concat _.toArray arguments)
-
-  # construct an <iframe> element, with `src` and `id`
-  iframeOptions:
-    frameborder: 0
-    border: 0
-    width: '100%'
-    height: '100%'
-    allowFullScreen: 'true'
-    webkitAllowFullScreen: 'true'
-    mozallowfullscreen: 'true'
-
-  # construct an <iframe> element, with `src` and `id`
-  iframe: (src, id) ->
-    f = $ '<iframe>'
-    _.map @iframeOptions, (val, key) ->
-      f.attr key, val
-    f.attr 'src', src
-    f.attr 'id', id if id?
-    f
-
-  # get the acorn variable in given <iframe> element
-  acornInIframe: (iframe) ->
-    iframe = iframe.get 0 if iframe.jquery?
-    win = iframe.contentWindow ? iframe.contentDocument.defaultView
-    win.acorn
-
-  # creates and returns a get/setter with a closured variable
-  property: (defaultValue, validate) ->
-    storedValue = defaultValue
-    validate ?= (x) -> x
-
-    (value) ->
-      storedValue = validate value if value?
-      storedValue
-
-  # requests full screen with given elem
-  fullscreen: (elem) ->
-    elem = elem[0] if elem.jquery?
-    if elem.requestFullscreen
-      elem.requestFullscreen()
-    else if elem.webkitRequestFullScreen
-      elem.webkitRequestFullScreen(Element.ALLOW_KEYBOARD_INPUT)
-    else if elem.mozRequestFullScreen
-      elem.mozRequestFullScreen()
-
-  # add acorn css
-  appendCss: (srcs) ->
-    srcs ?= acorn.config.css
-    srcs = [srcs] unless _.isArray(srcs)
-    _.each srcs, (src) ->
-      unless $("link[rel='stylesheet'][href='#{src}']").length
-        css = $('<link>')
-        css.attr 'rel', 'stylesheet'
-        css.attr 'href', src
-        $('body').append css
-
-  # converts human-readable timestring to seconds and back
-  # human-readable format is: [[hh:]mm:]ss[.SSS]
-  Time: class Time
-    constructor: (time) ->
-      @time = @constructor.timestringToSeconds time
-
-    seconds: => @time
-    timestring: => @constructor.secondsToTimestring @time
-
-    @timestringToSeconds: (timestring) =>
-      timestring = String(timestring ? 0)
-
-      # handle subsec [.SSS]
-      [rest, subsec] = timestring.split '.'
-      subsec = parseFloat "0.#{subsec ? '0'}"
-
-      # handle [[hh:]mm:]ss
-      rest = rest.split(':').reverse()
-      [sec, min, hrs] = _.map [0, 1, 2], (n) -> parseInt(rest[n], 10) or 0
-
-      # convert to seconds
-      (hrs * 60 * 60) + (min * 60) + sec + subsec
-
-    @secondsToTimestring: (seconds) =>
-      sec = parseInt seconds, 10
-
-      hrs = parseInt sec / (60 * 60), 10
-      sec -= hrs * 60 * 60
-
-      min = parseInt sec / 60, 10
-      sec -= min * 60
-
-      subsec = seconds % 1
-      if subsec
-        subsec = Math.round(subsec * 1000) / 1000
-        subsec = String(subsec).substr 1, 4 # remove first 0
-        subsec = subsec.replace /0+$/, ''
-
-      hrs = if hrs == 0 then '' else "#{hrs}:"
-      pad = (n) -> if n < 10 then "0#{n}" else "#{n}"
-
-      "#{hrs}#{pad min}:#{pad sec}#{subsec or ''}"
-
-  # Originally from StackOverflow
-  # http://stackoverflow.com/questions/736513
-  parseUrl: (url) ->
-    # simple `url` validation
-    # should extend to perform more comprehensive tests
-    ValueError 'url', 'should not be the empty string.' if url == ''
-
-    result = {}
-
-    # trim out any whitespace
-    url = $.trim url
-
-    # if no protocol is found, prepend http
-    url = "http://#{url}" unless /:\/\//.test url
-
-    anchor = document.createElement 'a'
-    anchor.href = url
-
-    keys = 'protocol hostname host pathname port search hash href'
-    (result[key] = anchor[key]) for key in keys.split ' '
-
-    # port-fix for phantomjs
-    result.port = '' if result.port == '0'
-
-    result.toString = -> result.href
-    result.resource = result.pathname + result.search
-    result.extension = result.pathname.split('.').pop()
-
-    result.head = -> throw new Error('head not supported. Yet.')
-
-    _.each result, (val, key) ->
-      if (not /_$/.test key) and (typeof(val) is 'string')
-        result[key + '_'] = val.toLowerCase()
-
-    result
-
-
-  # -- regular expressions
-
-  LINK_REGEX: /// ^
+util.isUrl = (url) ->
+  url = String(url)
+  @urlRegEx().test url
+
+util.isPath = (path) ->
+  /^[A-Za-z\/.-_]+$/.test path
+
+# helpers to construct acorn urls TODO: delete these?
+util.url = ->
+  path = _.toArray(arguments).join '/'
+  "http://#{acorn.config.domain}/#{path}"
+
+util.apiUrl = ->
+  apiPath = "api/v#{acorn.config.api.version}".split '/'
+  @url.apply(@, apiPath.concat _.toArray arguments)
+
+util.imgUrl = ->
+  @url.apply(@, ['img'].concat _.toArray arguments)
+
+# construct an <iframe> element, with `src` and `id`
+util.iframeOptions =
+  frameborder: 0
+  border: 0
+  width: '100%'
+  height: '100%'
+  allowFullScreen: 'true'
+  webkitAllowFullScreen: 'true'
+  mozallowfullscreen: 'true'
+
+# construct an <iframe> element, with `src` and `id`
+util.iframe = (src, id) ->
+  f = $ '<iframe>'
+  _.map @iframeOptions, (val, key) ->
+    f.attr key, val
+  f.attr 'src', src
+  f.attr 'id', id if id?
+  f
+
+# get the acorn variable in given <iframe> element
+util.acornInIframe = (iframe) ->
+  iframe = iframe.get 0 if iframe.jquery?
+  win = iframe.contentWindow ? iframe.contentDocument.defaultView
+  win.acorn
+
+# creates and returns a get/setter with a closured variable
+util.property = (defaultValue, validate) ->
+  storedValue = defaultValue
+  validate ?= (x) -> x
+
+  (value) ->
+    storedValue = validate value if value?
+    storedValue
+
+# requests full screen with given elem
+util.fullscreen = (elem) ->
+  elem = elem[0] if elem.jquery?
+  if elem.requestFullscreen
+    elem.requestFullscreen()
+  else if elem.webkitRequestFullScreen
+    elem.webkitRequestFullScreen(Element.ALLOW_KEYBOARD_INPUT)
+  else if elem.mozRequestFullScreen
+    elem.mozRequestFullScreen()
+
+# add acorn css
+util.appendCss = (srcs) ->
+  srcs ?= acorn.config.css
+  srcs = [srcs] unless _.isArray(srcs)
+  _.each srcs, (src) ->
+    unless $("link[rel='stylesheet'][href='#{src}']").length
+      css = $('<link>')
+      css.attr 'rel', 'stylesheet'
+      css.attr 'href', src
+      $('body').append css
+
+# converts human-readable timestring to seconds and back
+# human-readable format is: [[hh:]mm:]ss[.SSS]
+class util.Time
+  constructor: (time) ->
+    @time = @constructor.timestringToSeconds time
+
+  seconds: => @time
+  timestring: => @constructor.secondsToTimestring @time
+
+  @timestringToSeconds: (timestring) =>
+    timestring = String(timestring ? 0)
+
+    # handle subsec [.SSS]
+    [rest, subsec] = timestring.split '.'
+    subsec = parseFloat "0.#{subsec ? '0'}"
+
+    # handle [[hh:]mm:]ss
+    rest = rest.split(':').reverse()
+    [sec, min, hrs] = _.map [0, 1, 2], (n) -> parseInt(rest[n], 10) or 0
+
+    # convert to seconds
+    (hrs * 60 * 60) + (min * 60) + sec + subsec
+
+  @secondsToTimestring: (seconds) =>
+    sec = parseInt seconds, 10
+
+    hrs = parseInt sec / (60 * 60), 10
+    sec -= hrs * 60 * 60
+
+    min = parseInt sec / 60, 10
+    sec -= min * 60
+
+    subsec = seconds % 1
+    if subsec
+      subsec = Math.round(subsec * 1000) / 1000
+      subsec = String(subsec).substr 1, 4 # remove first 0
+      subsec = subsec.replace /0+$/, ''
+
+    hrs = if hrs == 0 then '' else "#{hrs}:"
+    pad = (n) -> if n < 10 then "0#{n}" else "#{n}"
+
+    "#{hrs}#{pad min}:#{pad sec}#{subsec or ''}"
+
+# Originally from StackOverflow
+# http://stackoverflow.com/questions/736513
+util.parseUrl = (url) ->
+  # simple `url` validation
+  # should extend to perform more comprehensive tests
+  ValueError 'url', 'should not be the empty string.' if url == ''
+
+  result = {}
+
+  # trim out any whitespace
+  url = $.trim url
+
+  # if no protocol is found, prepend http
+  url = "http://#{url}" unless /:\/\//.test url
+
+  anchor = document.createElement 'a'
+  anchor.href = url
+
+  keys = 'protocol hostname host pathname port search hash href'
+  (result[key] = anchor[key]) for key in keys.split ' '
+
+  # port-fix for phantomjs
+  result.port = '' if result.port == '0'
+
+  result.toString = -> result.href
+  result.resource = result.pathname + result.search
+  result.extension = result.pathname.split('.').pop()
+
+  result.head = -> throw new Error('head not supported. Yet.')
+
+  _.each result, (val, key) ->
+    if (not /_$/.test key) and (typeof(val) is 'string')
+      result[key + '_'] = val.toLowerCase()
+
+  result
+
+
+# -- regular expressions
+
+util.LINK_REGEX = /// ^
     https?://[-A-Za-z0-9+&@#/%?=~_()|!:,.;]*[-A-Za-z0-9+&@#/%=~_()|]
   ///

@@ -177,13 +177,13 @@
 # Preserve image aspect ratio but contain it wholly
 # See https://github.com/schmidsi/jquery-object-fit
 # setTimeout bypasses https://github.com/schmidsi/jquery-object-fit/issues/3
-acorn.util.fixObjectFit = ->
+util.fixObjectFit = ->
   objectFit_ = $.fn.objectFit
   $.fn.objectFit = ->
     setTimeout (=> objectFit_.apply @, arguments), 200
     @

-acorn.util.fixObjectFit()
+util.fixObjectFit()

 # inserts element at specific index


util: add utility for bounding a number

util: add utility for bounding a number

--- coffee/src/util/util.coffee
+++ coffee/src/util/util.coffee
@@ -150,6 +150,15 @@
   return false

+util.bound = (n, low, high) ->
+  unless high?
+    high = low ? 100
+    low = 0
+  if n < low then low
+  else if n > high then high
+  else n
+
+
 # Originally from StackOverflow
 # http://stackoverflow.com/questions/736513
 util.parseUrl = (url) ->


Fix 'undefined variable error' in visualization (closes #7)

--- coffee/visualization.coffee
+++ coffee/visualization.coffee
@@ -68,10 +68,10 @@
       else
         client.applyServer(o)

-    @server = new MyServer(str).appendTo(@el)
+    @server = new MyServer(@str).appendTo(@el)
     @aliceSendChannel = new NetworkChannel(true, serverReceive).appendTo(@el)
     @aliceSendChannel.el.attr({ id: 'alice-send-channel' })
-    @alice = new MyClient("Alice", str, 0, @aliceSendChannel)
+    @alice = new MyClient("Alice", @str, 0, @aliceSendChannel)
       .appendTo(@el)
     @alice.el.attr({ id: 'alice' })
     @alice.svg.attr('id', 'alice-diamond-diagram')
@@ -80,7 +80,7 @@

     @bobSendChannel = new NetworkChannel(true, serverReceive).appendTo(@el)
     @bobSendChannel.el.attr({ id: 'bob-send-channel' })
-    @bob = new MyClient("Bob", str, 0, @bobSendChannel)
+    @bob = new MyClient("Bob", @str, 0, @bobSendChannel)
       .appendTo(@el)
     @bob.el.attr({ id: 'bob' })
     @bob.svg.attr('id', 'bob-diamond-diagram')


Don't expose whether the user is registered on the frontend.

Consensus seems to have been that leaking this information is a Bad Thing.

--- js/inner/user.coffee
+++ js/inner/user.coffee
@@ -27,7 +27,6 @@
     email: null
     id: null
     following: false
-    registered: false

   constructor: (json) ->
     super json,


Bug with including babel in bundle fixed

--- src/bundle-builder/process.coffee
+++ src/bundle-builder/process.coffee
@@ -30,6 +30,7 @@
     return

 base = pathUtils.dirname index
+disabledFilenames = Object.create null

 mockGlobal require("./emulators/#{platform}") opts

@@ -47,6 +48,8 @@
 # register babel
 if opts.useBabel
     babelOptions = presets: ['es2015']
+    es2015Filename = Module._resolveFilename 'babel-preset-es2015', module
+    disabledFilenames[es2015Filename] = true
     require.extensions['.js'] = (module, filename) ->
         if filename.indexOf('node_modules') isnt -1
             return
@@ -82,6 +85,9 @@
         return r

     filename = Module._resolveFilename req, parent
+    if disabledFilenames[filename]
+        return r
+
     modulePath = pathUtils.relative base, filename
     parentPath = pathUtils.relative base, parent.id


--- boot.coffee
+++ boot.coffee
@@ -19,7 +19,7 @@
 async = require('async')

 global.etabits = {
-  baseUrl: 'https://www.svuonline.org/isis'
+  baseUrl: 'https://www.svuonline.org/SVUIS'
   log: log
   jsonMiddleware: require('body-parser').json()
   models: {}


Add notes

Add notes

--- client.coffee
+++ client.coffee
@@ -1088,7 +1088,7 @@
 				maxZoom = 17
 			window.map = L.map('OpenStreetMap', {center: [52.249822176849, 6.8396973609924], zoom: 13, zoomControl:false, updateWhenIdle:false, detectRetina:true, reuseTiles: true, minZoom: 3, maxZoom: maxZoom})

-			# Default OpenStreetMap tiles: maxZoom = 21
+			# Default OpenStreetMap tiles (quite busy and distracting)
 			###
 			L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
 				attribution: '© OpenStreetMap contributors'
@@ -1096,7 +1096,7 @@
 			}).addTo(map);
 			###

-			# CartoDB dark tiles: maxZoom = 18
+			# CartoDB dark tiles (not much contrast and brightness...)
 			###
 			L.tileLayer('https://cartodb-basemaps-{s}.global.ssl.fastly.net/dark_all/{z}/{x}/{y}.png', {
 				attribution: '© OpenStreetMap contributors, © CartoDB'


added delta from starting place to gene chart titles

--- demo.coffee
+++ demo.coffee
@@ -147,11 +147,14 @@
         rows: []
 geneChartSpec = ( type, gene, id ) ->
   title = type.charAt(0).toUpperCase() + type.substr(1)
+  original = u.urThing(type).genes[gene][0]
+  original = original() if typeof original == 'function'
   {
-    id: id
-    type: 'interval'
-    htitle: 'Time (ticks)'
-    vtitle: 'Value of gene'
+    id:       id
+    type:     'interval'
+    htitle:   'Time (ticks)'
+    vtitle:   'Value of gene'
+    original: original
     collector: (stats, rows) ->
       values = []
       values.push d.genes[gene] for d in stats when d.type == title
@@ -231,7 +234,7 @@
 drawChart = (ct=chartType)->
   return if ct == 'options'
   for title, specs of evoData.charts[ct] || {}
-    rows = trimData specs.rows
+    rows = specs.rows # trimData specs.rows
     return unless rows.length && rows[0].length
     id     = specs.id
     chart  = specs.chart
@@ -244,13 +247,20 @@
     options =
       title: titlize decamelize(title)
       width: width
+      explorer: axis: 'horizontal'
       height: height
       hAxis:
         title: htitle
+        viewWindow:
+          min: Math.max 1, rows.length - 500
+          max: rows.length
       vAxis:
         title: vtitle
     if specs.type == 'interval'
       data.addColumn 'number', 'values'
+      if specs.original?
+        delta = rows[ rows.length - 1 ][1] - specs.original
+        options.title += " (delta: #{trimNum delta})"
       ids = []
       for i in [2...5]
         data.addColumn id: "i#{i}", type: 'number', role: 'interval'


Make sure we always go back to the start panel.

--- js/flight.coffee
+++ js/flight.coffee
@@ -65,6 +65,8 @@
     else
       @targetPanel = targetPanel

+    if @targetPanel is @startPanel
+      @back = true
     if @pageHistory.length > 1 and window.location.hash is @pageHistory[@pageHistory.length - 2]
       @back = true


Begrudgingly use 2-column tabs when editing Ruby

Because the only time I write in Ruby is when it's for GitHub, lel.

--- config.cson
+++ config.cson
@@ -123,6 +123,9 @@
 	editor:
 		softTabs: true
 		tabLength: 2
+".ruby.source":
+	editor:
+		tabLength: 2
 ".rust.source":
 	editor:
 		softTabs: false


rounded time of Kal Nidre down to multiple of 5 minutes

--- js/mincha.coffee
+++ js/mincha.coffee
@@ -56,7 +56,7 @@
     else
       mincha: moment(sunset).subtract('minutes', 25), arbit: sunset
   else if hebrew_date.isErebYomKippur()
-    mincha: moment(sunset).hour(14).minute(30), arbit: sunset.subtract('minutes', 45)
+    mincha: moment(sunset).hour(14).minute(30), arbit: round_down_to_5_minutes(sunset.subtract('minutes', 45))
   else if hebrew_date.isEreb9Ab()
     if hebrew_date.isShabbat()
       $('.ereb-9-ab').removeClass('hidden')


made Mosaei YT not show up if it's a friday night

--- js/mincha.coffee
+++ js/mincha.coffee
@@ -94,7 +94,7 @@
       mincha: round_down_to_5_minutes(moment(earliest_arbit).subtract(30, 'minutes')), arbit: earliest_arbit
   else if hebrew_date.isYomTob()
     hadlakat_nerot_is_after_set_hakochabim(sunset, hebrew_date, zmanim)
-    mincha: round_down_to_5_minutes(sunset.subtract((if hebrew_date.isErebShabbat() then 40 else 25), 'minutes')), arbit: if hebrew_date.is1stDayOfYomTob() then null else (show_mosaei_yom_tob(sunset, zmanim); round_down_to_5_minutes(moment(zmanim.set_hakochabim).subtract(10, 'minutes')))
+    mincha: round_down_to_5_minutes(sunset.subtract((if hebrew_date.isErebShabbat() then 40 else 25), 'minutes')), arbit: if hebrew_date.is1stDayOfYomTob() || hebrew_date.isErebShabbat() then null else (show_mosaei_yom_tob(sunset, zmanim); round_down_to_5_minutes(moment(zmanim.set_hakochabim).subtract(10, 'minutes')))
   else if hebrew_date.isErebRoshHashana()
     mincha: round_down_to_5_minutes(sunset.subtract((if hebrew_date.isErebShabbat() then 55 else 45), 'minutes'))
   else if hebrew_date.isErebYomTob()


Start attempt of refactoring FunctionTransformer.

--- js2coffee.coffee
+++ js2coffee.coffee
@@ -46,7 +46,8 @@
   catch err
     throw buildError(err, source, options.filename)

-  Transformer.run(ast, options)
+  FunctionTransformer.run(ast, options)
+  OtherTransformer.run(ast, options)

   {code, map} = new Builder(ast, options).get()
   {code, ast, map}
@@ -56,6 +57,24 @@
 ###*
 # TransformerBase:
 # Base class.
+#
+#     class MyTransform extends TransformBase
+#       Program: (node) ->
+#         return { replacementNodeHere }
+#
+#       FunctionDeclaration: (node) ->
+#         ...
+#
+# From within the handlers, you can call some of the functions
+#
+#     @break()
+#     @skip()
+#     @syntaxError(node, "fail~)
+#
+# It also has a few hooks that you can override:
+#
+# ~ onScopeEnter: when scopes are entered (via `pushScope()`)
+# ~ onScopeExit: when scopes are exited (via `popScope()`)
 ###

 class TransformerBase
@@ -95,13 +114,6 @@
   break: ->
     @controller?.break()

-  # Call these onEnter() and onExit() for debugging
-  debugEnter: (node, depth, fn) ->
-    console.log Array(depth+1).join("  "), node.type, (if fn then "*" else "")
-
-  debugExit: (node, depth, fn) ->
-    console.log Array(depth+1).join("  "), ""+node.type+"Exit", (if fn then "*" else "")
-
   ###*
   # estraverse():
   # Returns `estraverse`.
@@ -125,8 +137,10 @@
     @scopes.push [ node, @ctx ]
     @ctx = clone(@ctx)
     @scope = node
+    @onScopeEnter?(node, @ctx)

   popStack: (node) ->
+    @onScopeExit?(node, @ctx)
     [ @scope, @ctx ] = @scopes.pop()
     @parent = if @scopes.length > 1 then @scopes[@scopes.length-2]
     node
@@ -146,6 +160,7 @@
     , @options.source, @options.filename)
     throw err

+
 # ----------------------------------------------------------------------------

 ###*
@@ -153,10 +168,9 @@
 # Mangles the AST.
 ###

-class Transformer extends TransformerBase
+class OtherTransformer extends TransformerBase
   Program: (node) ->
     @pushStack node
-    new FunctionTransformer(node, @options).run(node)
   ProgramExit: (node) ->
     @popStack()
   BlockStatementExit: (node) ->
@@ -169,7 +183,6 @@
     throw new Error("NamedFnExpr: Not supposed to happen") if node.id # fixScope should've gotten this case
     @pushStack node.body
     @removeUndefinedParameter node
-    new FunctionTransformer(node, @options).run(node.body)
   FunctionExpressionExit: (node) ->
     @popStack()
   SwitchStatement: (node) ->
@@ -401,25 +414,37 @@
 ###

 class FunctionTransformer extends TransformerBase
-  run: (@body) ->
+  onScopeEnter: (node) ->
     @prebody = []
-    @recurse @ast
-
+
+  onScopeExit: (node) ->
     if @prebody.length
-      @body.body = @prebody.concat(@body.body)
-
-    @ast
+      @scope.body = @prebody.concat(@scope.body)
+
+  Program: (node) ->
+    @pushStack node
+
+  ProgramExit: (node) ->
+    @popStack()

   FunctionDeclaration: (node) ->
+    @pushStack node.body
     @skip()
     @prebody.push @buildFunctionDeclaration(node)
     { type: 'EmptyStatement' }

   FunctionExpression: (node) ->
+    @pushStack node.body
     return unless node.id?
     @skip()
     @prebody.push @buildFunctionDeclaration(node)
     { type: 'Identifier', name: node.id.name }
+
+  FunctionDeclarationExit: (node) ->
+    @popStack()
+
+  FunctionExpressionExit: (node) ->
+    @popStack()

   ###
   # Returns a `a = -> ...` statement out of a FunctionDeclaration node.
@@ -1003,6 +1028,33 @@
       left = item.range[1]
   list

+# ----------------------------------------------------------------------------
+
+###
 # Export for testing
+###
+
 js2coffee.Builder = Builder
 js2coffee.BuilderBase = BuilderBase
+
+# ----------------------------------------------------------------------------
+
+###
+# Debugging provisions
+###
+
+js2coffee.debug = ->
+  isDead = (ast) ->
+    output = require('util').inspect(ast, depth: 1000)
+    if ~output.indexOf("[Circular]")
+      "[CIRCULAR]"
+    else
+      ""
+
+  TransformerBase::onEnter = (node, depth, fn) ->
+    console.log Array(depth+1).join("  "), node.type, (if fn then "*" else ""), isDead(@ast)
+
+  TransformerBase::onExit = (node, depth, fn) ->
+    console.log Array(depth+1).join("  "), ""+node.type+"Exit", (if fn then "*" else ""), isDead(@ast)
+
+js2coffee.debug()


:fire: Remove Linter::getEditorLinterByPath

--- lib/linter.coffee
+++ lib/linter.coffee
@@ -79,9 +79,6 @@
   getEditorLinter: (editor) ->
     @editors.ofTextEditor(editor)

-  getEditorLinterByPath: (path) ->
-    @editors.ofPath(path)
-
   eachEditorLinter: (callback) ->
     @editors.forEach(callback)


:fire: intermediate class which is not really necessary.


Use of, not in

Use of, not in

--- lib/script.coffee
+++ lib/script.coffee
@@ -15,7 +15,11 @@
   activate: ->
     atom.project.registerOpener (uri) =>

-      if @lang?
+      console.log(@lang?)
+      console.log(@lang in grammarMap)
+      console.log(grammarMap)
+
+      if @lang? and @lang of grammarMap
         interpreter = grammarMap[@lang]["interpreter"]
         makeargs = grammarMap[@lang]["makeargs"]

@@ -46,7 +50,7 @@

       # TODO: Provide them a dialog to submit an issue on GH, prepopulated
       #       with their language of choice
-      if ! grammar.name in grammarMap
+      if ! (grammar.name of grammarMap)
         console.log("Interpreter not configured for " + @lang)
         console.log("Send a pull request to add support!")
         return


WIP on porting map marker code from old Glossa

--- app/assets/javascripts/rglossa/components/centre/results/geo_distribution_map.jsx.coffee
+++ app/assets/javascripts/rglossa/components/centre/results/geo_distribution_map.jsx.coffee
@@ -1,30 +1,253 @@
 ###* @jsx React.DOM ###
+
+# MarkerSets - an array for all MarkerSet objects
+
+class MarkerSets
+  @sets: []
+  @missing: []
+  @infoWindow: null
+
+  add: (tok, locas, color) ->
+    if @sets[tok]
+      @sets[tok].remove()
+      @sets[tok].color = color
+      @sets[tok].add()
+    else
+      set = new MarkerSet(locas, tok, color)
+      set.add()
+      @sets[tok] = set
+      for loc of set.noCoords
+        @missing[loc] = 1
+
+  hide: ->
+    for j of @sets
+      @sets[j].hide()
+
+  show: ->
+    for j of @sets
+      @sets[j].show()
+
+  remove: (tok) ->
+    @sets[tok].remove() if @sets[tok]
+
+  removeAll: ->
+    for j of @sets
+      @sets[j].remove()
+
+  report: ->
+    r = ""
+    for e of @missing
+      r += " " + e
+    r
+
+  closeInfoWindows: ->
+    for j of @sets
+      @sets[j].closeInfoWindow()
+
+
+# MarkerSet - an object for groups of markers of same colour.
+
+class MarkerSet
+  constructor: (locas, tok, color) ->
+    @locations = locas   # an array of locations for marker set
+    @color = color       # the chosen marker color
+    @tok = tok
+    @markers = []        # array for all google marker objects in this set
+    @noCoords = []
+
+
+  createMarker: (latlng, map, color, contentString, loc) ->
+    image = new google.maps.MarkerImage(
+      "../html/img/mm_20_#{color}.png",
+      new google.maps.Size(8, 13),    # size
+      new google.maps.Point(0, 0),    # origin
+      new google.maps.Point(4, 13),   # anchor
+      new google.maps.Size(8, 13))    # size
+
+    shadow = new google.maps.MarkerImage(
+      "../html/img/mm_20_shadow.png",
+      new google.maps.Size(12, 16),   # size
+      new google.maps.Point(0, 0),    # origin
+      new google.maps.Point(4, 13),   # anchor
+      new google.maps.Size(12, 16))   # size
+
+    infowindow = new google.maps.InfoWindow(content: contentString)
+
+    marker = new google.maps.Marker
+      position: latlng
+      map: map
+      icon: image
+      shadow: shadow
+      animation: google.maps.Animation.DROP
+      title: loc
+
+    google.maps.event.addListener marker, "click", ->
+      MarkerSet.infoWindow.close() if MarkerSet.infoWindow?
+      MarkerSet.infoWindow = infowindow
+      infowindow.open map, marker
+
+    marker
+
+
+  closeInfoWindow: ->
+    for j of @markers
+      alert "closing: " + @markers[j].title
+      if @markers[j].infoWindow
+        alert "i have an infowindow!"
+        @markers[j].infoWindow.close()
+
+
+  add: ->
+    for loc of @locations
+      unless coordinates[loc]
+        @noCoord loc
+        continue
+      lat = coordinates[loc]["lat"]
+      lng = coordinates[loc]["lng"]
+      latlng = new google.maps.LatLng(lat, lng)
+      marker = @createMarker(latlng, map, @color, makeMessage(loc, @tok), loc)
+      @markers.push marker
+
+  hide: ->
+    for j of @markers
+      @markers[j].setMap null
+    true
+
+  show: ->
+    for j of @markers
+      @markers[j].setMap map
+
+  remove: ->
+    for j of @markers
+      @markers[j].setMap null
+    @markers = []
+
+  noCoord: (loc) ->
+    @noCoords[loc] = 1
+
+MarkerSet.infoWindow = null
+

 window.GeoDistributionMap = React.createClass
   propTypes:
     data: React.PropTypes.object

+  markerSets: new MarkerSets()
+  loc_inf: []
+  allMarkers: []
+
+  addMarker: (loca) ->
+    dot = new google.maps.MarkerImage(
+      "../html/img/red_dot.png",
+      new google.maps.Size(4, 4),   # size
+      new google.maps.Point(0, 0),  # origin
+      new google.maps.Point(2, 2),  # anchor
+      new google.maps.Size(4, 4))   # size
+
+    unless coordinates[loca]
+      document.getElementById("noCoords").innerHTML =
+          "No coordinates found for: #{loca}"
+      return loca
+
+    lat = coordinates[loca]["lat"]
+    lng = coordinates[loca]["lng"]
+
+    latlng = new google.maps.LatLng(lat, lng)
+    marker = new google.maps.Marker
+      position: latlng
+      map: map
+      icon: dot
+
+    allMarkers.push marker
+    true
+
+
+  addMarkers: (color, tok) ->
+    if color is "#ddd"
+      @markerSets.remove(tok)
+      return
+
+    locas = toksLocs[tok] #toksLocs[tok] is.. an Array!
+    i = 0
+    noCoords = []
+    @markerSets.add(tok, locas, color) # need to use this to remove, also!!!
+    report @markerSets.missing
+
+
+  makeMessage: (loc, tok) ->
+    infs1 = loc_inf[loc]
+    infs2 = tok_inf[tok]
+    infs = intersection(infs1, infs2)
+
+    "Location: #{loc}
Phonetic: #{tok}
Informants: #{profileLink(infs)}"
+
+
+  # for reporting all missing coordinates
+  report: (arr) ->
+    empty = true
+
+    unless arr[0]?
+      for i of arr
+        empty = false
+
+    if empty
+      document.getElementById("noCoords").innerHTML = ""
+      return
+
+    document.getElementById("noCoords").innerHTML = "Missing coordinates for: "
+    for i of arr
+      continue  if i is "unique"
+      document.getElementById("noCoords").innerHTML += i + " "
+
+
+  intersection: (arr1, arr2) ->
+    is_ = []
+    for j of arr1
+      for i of arr2
+        if arr1[j] is arr2[i]
+          is_.push(arr1[j])
+          break
+    is_
+
+  removePlots: ->
+    @markerSets.removeAll()
+    $(".colorpicker-trigger").css "background-color", "#ddd"
+
+  hidePlots: ->
+    @markerSets.hide()
+
+  showPlots: ->
+    @markerSets.show()
+
+  clearMap: ->
+    for j of allMarkers
+      allMarkers[j].setMap null
+
+  showAll: ->
+    for j of allLocations
+      addMarker allLocations[j]
+
   render: ->
     points = if @props.data
       (for key, value of @props.data.place_totals
-          # TODO: Perhaps store coordinates in the database? We currently expect an application
-          # using rglossa to define a global 'coordinates' object where the keys are location names
-          # and the values are objects with 'lat' and 'lng' keys. A nice effect of this is that
-          # it is easy to share coordinate values between applications (Glossa applications
-          # as well as others), since all they have to do is load a JavaScript file that defines
-          # such an object.
-          # The downside is that the coordinates are not available on the server side, where we
-          # we might want to use them e.g. for statistical calculations in the future. Hence, we
-          # might want to move them to the database instead. We need to create tables that link
-          # locations with speakers anyway (since the joins we do now in
-          # SpeechCwbSearch#find_tids_to_places are far too costly when we have results from lots
-          # of speakers), and then we could probably just store the coordinates in the locations
-          # table.
-          coord = coordinates[key]
-
-          latitude: coord.lat
-          longitude: coord.lng
-          label: key)
+        # TODO: Perhaps store coordinates in the database? We currently expect an application
+        # using rglossa to define a global 'coordinates' object where the keys are location names
+        # and the values are objects with 'lat' and 'lng' keys. A nice effect of this is that
+        # it is easy to share coordinate values between applications (Glossa applications
+        # as well as others), since all they have to do is load a JavaScript file that defines
+        # such an object.
+        # The downside is that the coordinates are not available on the server side, where we
+        # we might want to use them e.g. for statistical calculations in the future. Hence, we
+        # might want to move them to the database instead. We need to create tables that link
+        # locations with speakers anyway (since the joins we do now in
+        # SpeechCwbSearch#find_tids_to_places are far too costly when we have results from lots
+        # of speakers), and then we could probably just store the coordinates in the locations
+        # table.
+        coord = coordinates[key]
+
+        latitude: coord.lat
+        longitude: coord.lng
+        label: key)
     else []

     `


Remove dependency between @inviting[user.login] and user.email.

This is what was causing the weird bug where entering anything into
the email <input/> would check the box.

Knockout is weird !

--- js/inner/build.coffee
+++ js/inner/build.coffee
@@ -777,7 +777,7 @@
     # Prepopulate the list of team members to invite with the ones
     # whose email addresses we already know.
     for user in users
-      @inviting[user.login] = @observable !!user.email()
+      @inviting[user.login] = @observable !!user.email.peek()

   send: () =>
     to_invite = user for user in @users when @inviting[user.login]() and user.email()


renamed some things in layer stack

renamed some things in layer stack

--- js/layer_stack.coffee
+++ js/layer_stack.coffee
@@ -6,10 +6,10 @@
     @stack = new Lysertron.LayerGroup this
     super

-  push: (echoes...) ->
-    for echo in echoes
-      @add echo
-      @stack.push echo
+  push: (layers...) ->
+    for layer in layers
+      @add layer
+      @stack.push layer
     return

   dispatchMusicEvent: (data) ->


added function to provide javascript inheritance

--- lib/baseclass.coffee
+++ lib/baseclass.coffee
@@ -11,4 +11,18 @@
       DocumentClass.Mixins.get(mixin)?.applyMixin this
   @mixinArray @defaultMixins

+  @extend: (extension) ->
+    Constructor = null
+    Extended = class extends this
+      constructor: ->
+        if Constructor? then Constructor.apply(this, arguments) else super
+
+    if typeof(extension) is 'function'
+      extension = extension.call Extended
+    else
+      extension = extension ? {}
+    Constructor = extension.Constructor ? null
+    Extended.prototype[key] = extension[key] for key of extension
+    return Extended
+
   constructor: (doc) -> _.extend this, doc


fix default environment value

fix default environment value

--- lib/cli/index.coffee
+++ lib/cli/index.coffee
@@ -125,7 +125,7 @@
       help: "Path to a project that you would like to watch"

     s.addArgument ['--env', '-e'],
-      defaultValue: process.env or 'development'
+      defaultValue: process.env['NODE_ENV'] or 'development'
       help: "Your project's environment"

     s.addArgument ['--no-open'],
@@ -154,7 +154,7 @@
       help: "Path to a project that you would like to compile"

     s.addArgument ['--env', '-e'],
-      defaultValue: process.env or 'development'
+      defaultValue: process.env['NODE_ENV'] or 'development'
       help: "Your project's environment"

     s.addArgument ['--verbose', '-v'],


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com


Don't show link hints for offscreen image maps

--- lib/dom_utils.coffee
+++ lib/dom_utils.coffee
@@ -52,18 +52,9 @@
     } for clientRect in element.getClientRects())

     for clientRect in clientRects
-      if (clientRect.top < 0)
-        clientRect.height += clientRect.top
-        clientRect.top = 0
+      clientRect = @cropRectToVisible clientRect

-      if (clientRect.left < 0)
-        clientRect.width += clientRect.left
-        clientRect.left = 0
-
-      if (clientRect.top >= window.innerHeight - 4 || clientRect.left  >= window.innerWidth - 4)
-        continue
-
-      if (clientRect.width < 3 || clientRect.height < 3)
+      if (!clientRect || clientRect.width < 3 || clientRect.height < 3)
         continue

       # eliminate invisible elements (see test_harnesses/visibility_test.html)
@@ -90,6 +81,21 @@
           return childClientRect
     null

+  cropRectToVisible: (rect) ->
+    if (rect.top < 0)
+      rect.height += rect.top
+      rect.top = 0
+
+    if (rect.left < 0)
+      rect.width += rect.left
+      rect.left = 0
+
+    if (rect.top >= window.innerHeight - 4 || rect.left  >= window.innerWidth - 4)
+      null
+    else
+      rect
+
+
   getClientRectsForAreas: (imgClientRect, areas) ->
     rects = []
     for area in areas
@@ -108,7 +114,7 @@
         # something more sophisticated, but likely not worth the effort.
         [x1, y1, x2, y2] = coords

-      rect =
+      rect = @cropRectToVisible
         top: imgClientRect.top + y1
         left: imgClientRect.left + x1
         right: imgClientRect.left + x2
@@ -116,7 +122,7 @@
         width: x2 - x1
         height: y2 - y1

-      rects.push {element: area, rect: rect} unless isNaN rect.top
+      rects.push {element: area, rect: rect} unless not rect or isNaN rect.top
     rects

   #


Update the upstream URL in the comments to refer to version used for the latest merge.

--- lib/languages.coffee
+++ lib/languages.coffee
@@ -5,7 +5,7 @@
 #
 # @copyright Ian MacLeod and groc contributors
 #
-# [1]: https://github.com/nevir/groc/blob/b626e45ebf/lib/languages.coffee
+# [1]: https://github.com/nevir/groc/blob/5495be0a25/lib/languages.coffee
 ###

 module.exports = LANGUAGES =


sgconfigure html and jsp

sgconfigure html and jsp

--- lib/languages.coffee
+++ lib/languages.coffee
@@ -126,6 +126,13 @@
     ignorePrefix:      '}'
     foldPrefix:        '^'

+  HTML:
+    nameMatchers:      ['.htm', '.html']
+    pygmentsLexer:     'html'
+    multiLineComment:  ['<!--', '', '-->']
+    ignorePrefix:      '}'
+    foldPrefix:        '^'
+
   Jade:
     nameMatchers:      ['.jade']
     pygmentsLexer:     'jade'
@@ -165,7 +172,8 @@
     nameMatchers:      ['.jsp']
     pygmentsLexer:     'jsp'
     multiLineComment:  [
-      '<%--', '', '--%>'
+      '<%--', '', '--%>',
+      '<!--', '', '-->'
     ]
     strictMultiLineEnd:true
     ignorePrefix:      '#'


Debugging.

Debugging.


PopUp fadeOut-animation improved.

PopUp fadeOut-animation improved.

--- lib/atox-PopUp.coffee
+++ lib/atox-PopUp.coffee
@@ -20,6 +20,7 @@
     @currentID = 0
     @PopUps = []
     @run = true
+    @lts = 0

     atom.views.getView atom.workspace
       .appendChild @element
@@ -30,17 +31,29 @@

     temp.hide()
     temp.appendTo this
-    temp.fadeIn   atom.config.get 'atox.fadeDuration'
+    temp.fadeIn atom.config.get 'atox.fadeDuration'

+    stimeout = ( atom.config.get 'atox.popupTimeout' ) * 1000
+    aduration = ( atom.config.get 'atox.fadeDuration' ) + 1200
+    if ((Date.now() + stimeout) - @lts) < aduration
+      timeout = stimeout + aduration - ((Date.now() + stimeout) - @lts)
+    else
+      timeout = stimeout
     setTimeout =>
        @shift()
-    , ( atom.config.get 'atox.popupTimeout' ) * 1000
+    , timeout
+    @lts = Date.now() + timeout

     @PopUps.push temp

   shift: ->
     temp = @PopUps[0]
     @PopUps.shift()
-
-    temp.fadeOut (atom.config.get 'atox.fadeDuration'), =>
-       temp.remove()
+    console.log (atom.config.get 'atox.fadeDuration')
+    temp.animate {opacity: 0}, (atom.config.get 'atox.fadeDuration'), =>
+       e.css ({position: 'relative'}) for e in @PopUps
+       e.animate {top: '-75px'}, 1000 for e in @PopUps
+       setTimeout =>
+         temp.remove()
+         e.css {position: 'static', top: '0'} for e in @PopUps
+       , 1100


Fixup configuration schema.

Fixup configuration schema.

--- lib/dart-tools.coffee
+++ lib/dart-tools.coffee
@@ -10,9 +10,13 @@

   # Wizardry
-  configDefaults:
-    automaticPubGet: true
-    dartSdkLocation: ''
+  config:
+    automaticPubGet:
+      type: 'boolean'
+      default: true
+    dartSdkLocation:
+      type: 'string'
+      default: ''

   # Provider for `autocomplete-plus`


client-side half of mts deployment (the easy part)

--- lib/deployment.coffee
+++ lib/deployment.coffee
@@ -1,5 +1,6 @@
 child_process = require 'child_process'
 os = require 'os'
+control = require 'control'
 shell = require 'shelljs'

 module.exports = patch: (cls) ->
@@ -13,7 +14,10 @@
                 # can't use shell.exec for this due to password input
                 child_process.spawn 'meteor', ['deploy', @config.deployment.server], {stdio: 'inherit'}
             when 'mts', undefined
-                throw new RunError 'Sorry, deployment with server-side mts not yet implemented.'
+                controller = Object.create control.controller
+                controller.address = @config.deployment.server
+                controller.user = @config.deployment.user
+                controller.ssh "cd \"#{@config.deployment.workspace}\" && mts deploy"
             else
                 throw new RunError "Unknown deployment method #{@config.deployment.method}"
     cls::deploy.is_command = true


Open guide items in left split

Open guide items in left split

--- lib/guide-view.coffee
+++ lib/guide-view.coffee
@@ -114,16 +114,15 @@
     @projectButton.on 'click', =>
       atom.commands.dispatch(atom.views.getView(atom.workspace), 'application:open')
     @packagesButton.on 'click', =>
-      atom.commands.dispatch(atom.views.getView(atom.workspace), 'settings-view:install-packages-and-themes')
+      atom.workspace.open('atom://config/install', split: 'left')
     @themesButton.on 'click', =>
-      atom.commands.dispatch(atom.views.getView(atom.workspace), 'settings-view:change-themes')
+      atom.workspace.open('atom://config/themes', split: 'left')
     @stylingButton.on 'click', =>
-      atom.commands.dispatch(atom.views.getView(atom.workspace), 'application:open-your-stylesheet')
+      atom.workspace.open('atom://.atom/stylesheet', split: 'left')
     @initScriptButton.on 'click', =>
-      atom.commands.dispatch(atom.views.getView(atom.workspace), 'application:open-your-init-script')
+      atom.workspace.open('atom://.atom/init-script', split: 'left')
     @snippetsButton.on 'click', =>
-      atom.commands.dispatch(atom.views.getView(atom.workspace), 'application:open-your-snippets')
-
+      atom.workspace.open('atom://.atom/snippets', split: 'left')

   @deserialize: (options={}) ->
     new GuideView(options)


change location of livereload.js client code

--- lib/livereload.coffee
+++ lib/livereload.coffee
@@ -106,7 +106,7 @@
   app = http.createServer ( req, res )->
     if url.parse(req.url).pathname is '/livereload.js'
       res.writeHead(200, {'Content-Type': 'text/javascript'})
-      res.end fs.readFileSync __dirname + '/../livereload-js/dist/livereload.js'
+      res.end fs.readFileSync __dirname + '/../ext/livereload.js'

   app.listen(defaultPort)
   config.server = app


[setter] use name of variable instead of '$value'

--- lib/my-package.coffee
+++ lib/my-package.coffee
@@ -28,9 +28,9 @@
 \ \ \ \ * \n
 \ \ \ \ * @return self\n
 \ \ \ \ */\n
-\ \ \ %scope% function %methodName%(%typeHint%$value)\n
+\ \ \ %scope% function %methodName%(%typeHint%$%variable%)\n
 \ \ \ {\n
-\ \ \ \ \ \ \ $this->%variable% = $value;\n
+\ \ \ \ \ \ \ $this->%variable% = $%variable%;\n
 \n
 \ \ \ \ \ \ \ return $this;\n
 \ \ \ }\n


small fix

small fix

--- lib/sshfs-node.coffee
+++ lib/sshfs-node.coffee
@@ -31,7 +31,7 @@
 sshfs.mount = (host, mountpoint, options, callback) ->
   identityOption = ''
   if options && options.identityFile
-    identityOption = util.format '-o %s', options.identityFile
+    identityOption = util.format '-o IdentityFile=%s', options.identityFile

   userOption = ''
   if options && options.user


only change ttl on cached packs, not temporary ones

temporary = without versioning feature enabled
cached = permanent versioned retrieved from s3

--- app/coffee/PackManager.coffee
+++ app/coffee/PackManager.coffee
@@ -204,10 +204,12 @@
 			return callback(err) if err?
 			if not pack?
 				MongoAWS.unArchivePack project_id, doc_id, pack_id, callback
-			else if pack.expiresAt?
+			else if pack.expiresAt? and not pack.temporary
 				# we only need to touch the TTL on the listing of changes in the project
 				# because diffs on individual documents are always done after that
 				PackManager.increaseTTL pack, callback
+				# only do this for cached packs, not temporary ones to avoid older packs
+				# being kept longer than newer ones (which messes up the last update version)
 			else
 				callback(null, pack)


fix exception when content undefined in TikzManager

--- app/coffee/TikzManager.coffee
+++ app/coffee/TikzManager.coffee
@@ -22,7 +22,7 @@
 			SafeReader.readFile path, 65536, "utf8", (error, content) ->
 				return callback(error) if error?
 				usesTikzExternalize = content?.indexOf("\\tikzexternalize") >= 0
-				usesPsTool = content.indexOf("{pstool}") >= 0
+				usesPsTool = content?.indexOf("{pstool}") >= 0
 				logger.log compileDir: compileDir, mainFile: mainFile, usesTikzExternalize:usesTikzExternalize, usesPsTool: usesPsTool, "checked for packages needing main file as output.tex"
 				needsMainFile = (usesTikzExternalize || usesPsTool)
 				callback null, needsMainFile


Add configuration for map

Add configuration for map

--- app/config/tool_config.coffee
+++ app/config/tool_config.coffee
@@ -41,6 +41,7 @@
     titleBarControls: true
   "Mapper" :
     settings: [settings.data, settings.map]
+    defaults: {spectrum: 'visible'}
   "Table" :
     settings: [settings.data, settings.subject]
     defaults: {sortOrder: 'top', currentPage: 0, sortColumn: 'uid'}


use TimeWithTooltip instead

use TimeWithTooltip instead

--- resources/assets/coffee/react/profile-page/account-standing.coffee
+++ resources/assets/coffee/react/profile-page/account-standing.coffee
@@ -2,6 +2,7 @@
 # See the LICENCE file in the repository root for full licence text.

 import { ExtraHeader } from './extra-header'
+import TimeWithTooltip from 'time-with-tooltip'
 import * as React from 'react'
 import { a, div, span, h3, table, thead, tbody, tr, th, td } from 'react-dom-factories'
 el = React.createElement
@@ -61,8 +62,8 @@
         key: i

         td
-          className: "#{bn}__table-cell #{bn}__table-cell--date", dangerouslySetInnerHTML:
-            __html: osu.timeago(event.timestamp)
+          className: "#{bn}__table-cell #{bn}__table-cell--date",
+            el TimeWithTooltip, dateTime: event.timestamp, relative: true

         td
           className: "#{bn}__table-cell #{bn}__table-cell--action"


fix equation editor when opening multiple times

test plan:
* using the rich text editor, use the equation editor
to insert an equation
* click again to insert another equation (should start
with a blank equation editor)
* should be able to insert another equation instead
of receiving an "undefined" message

fixes #CNVS-10242

Change-Id: I7d07a751a8e4fa454eec616d9fadeac01ad8e760
Reviewed-on: https://gerrit.instructure.com/28172
Reviewed-by: Jon Jensen <44f878afe53efc66b76772bd845eb65944ed8232@instructure.com>
Tested-by: Jenkins <d95b56ce41a2e1ac4cecdd398defd7414407cc08@instructure.com>
QA-Review: Nathan Rogowski <2e8aa918660411855c6d44d5bb2da677aa033255@instructure.com>
Product-Review: James Williams  <3a5f4ed991dbfa5c9ef76ad729d240ca12f62180@instructure.com>

--- app/coffeescripts/views/tinymce/EquationEditorView.coffee
+++ app/coffeescripts/views/tinymce/EquationEditorView.coffee
@@ -42,7 +42,6 @@
       '#mathjax-message': '$mathjaxMessage'

     initialize: (@editor) ->
-      super
       @$editor = $("##{@editor.id}")
       @prevSelection = @editor.selection.getBookmark()


resurrection.activity: map tags object in token expansion procedure

--- client/Social/Activity/ListItems/ActivityListItemStatusUpdate.coffee
+++ client/Social/Activity/ListItems/ActivityListItemStatusUpdate.coffee
@@ -27,8 +27,6 @@

     @timeAgoView = new KDTimeAgoView {}, @getData().meta.createdAt

-    @tags = @getTokenMap(data.tags) or {}
-
   getTokenMap: (tokens) ->
     return  unless tokens
     map = {}
@@ -39,12 +37,13 @@
     return  str unless tokenMatches = str.match /\|.+?\|/g

     data = @getData()
+    tagMap = @getTokenMap data.tags  if data.tags

     for tokenString in tokenMatches
       [prefix, constructorName, id] = @decodeToken tokenString

       switch prefix
-        when "#" then token = @tags[id]
+        when "#" then token = tagMap?[id]
         else continue

       continue  unless token


fixed view

fixed view

--- app/assets/javascripts/view.coffee
+++ app/assets/javascripts/view.coffee
@@ -80,7 +80,7 @@

 		#renders all geometries in geometry-array
 		totalNumberOfVertices = 0
-		#totalNumberOfVertices += drawTriangleplane()
+		totalNumberOfVertices += drawTriangleplane()

 		# first Mesh is always the coordinate axis mini-map
 		if meshes[0]


I think this is how the new exports'll work

--- app/pages/lab/project-details.cjsx
+++ app/pages/lab/project-details.cjsx
@@ -87,7 +87,7 @@
       .catch ->
         ''

-    @classificationsExportsGet ?= @props.project.get 'classifications_exports'
+    @classificationsExportsGet ?= @props.project.get 'classifications_export'
       .catch ->
         []

@@ -158,9 +158,9 @@
           <button type="button" disabled={@state.exportRequested} onClick={@requestDataExport}>Request data export</button>{' '}

             CSV format available.{' '}
-            <PromiseRenderer promise={@classificationsExportsGet} then={([mostRecent]) =>
+            <PromiseRenderer promise={@classificationsExportsGet} then={(mostRecent) =>
               if mostRecent?
-                Most recent request was made {moment(mostRecent.created_at).fromNow()}.
+                Most recent request was made {moment(mostRecent.created_at).fromNow()}.
               else
                 Never requested.
             } />

@@ -202,7 +202,7 @@

   requestDataExport: ->
     @setState exportError: null
-    apiClient.post @props.project._getURL('classifications_exports'), media: content_type: 'text/csv'
+    apiClient.post @props.project._getURL('classifications_export'), media: content_type: 'text/csv'
       .then =>
         @classificationsExportsGet = null
         @setState exportRequested: true


handle stdin in the case of remote terms

--- lib/term-view.coffee
+++ lib/term-view.coffee
@@ -38,6 +38,12 @@

   onResize: (callback) ->
     @emitter.on 'resize', callback
+
+  onSTDIN: (callback) ->
+    @emitter.on 'stdin', callback
+
+  onSTDOUT: (callback) ->
+    @emitter.on 'stdout', callback

   input: (data) ->
     try
@@ -61,10 +67,17 @@
     @term = term = new Terminal {
       useStyle: no
       screenKeys: no
+      handler: (data) =>
+        @emitter.emit 'stdin', data
       colors, cursorBlink, scrollback, cols, rows
     }

-    term.on "data", (data) => @input data
+    term.on "data", (data) =>
+      @emitter.emit 'stdout', data
+      # let the remote term write to stdin - we slurp up its stdout
+      if @ptyProcess
+        @input data
+
     term.on "title", (title) =>
       if title.length > 20
         split = title.split(path.sep)


:art: `g v` now correctly restore vL with cursor column

--- lib/vim-state.coffee
+++ lib/vim-state.coffee
@@ -329,7 +329,11 @@
     if @isMode('visual', 'blockwise')
       properties = @getLastBlockwiseSelection()?.getCharacterwiseProperties()
     else
-      properties = swrap(@editor.getLastSelection()).captureProperties()
+      selection = @editor.getLastSelection()
+      if swrap(selection).hasProperties()
+        properties = swrap(selection).getProperties()
+      else
+        properties = swrap(@editor.getLastSelection()).captureProperties()

     return unless properties?

@@ -338,6 +342,7 @@
       @mark.setRange('<', '>', [tail, head])
     else
       @mark.setRange('<', '>', [head, tail])
+
     @previousSelection = {properties, @submode}

   # Persistent selection


add class for status bar (command, insert, visual)

--- lib/vim-state.coffee
+++ lib/vim-state.coffee
@@ -481,11 +481,16 @@
   updateStatusBar: ->
     if !$('#status-bar-vim-mode').length
       atom.packages.once 'activated', ->
-        atom.workspaceView.statusBar?.prependRight("
Command
")
+        atom.workspaceView.statusBar?.prependRight("
Command
")

     if @mode is "insert"
-      $('#status-bar-vim-mode').html("Insert")
+      @removeStatusBarClass()
+      $('#status-bar-vim-mode').addClass('status-bar-vim-mode-insert').html("Insert")
     else if @mode is "command"
-      $('#status-bar-vim-mode').html("Command")
+      @removeStatusBarClass()
+      $('#status-bar-vim-mode').addClass('status-bar-vim-mode-command').html("Command")
     else if @mode is "visual"
-      $('#status-bar-vim-mode').html("Visual")
+      @removeStatusBarClass()
+      $('#status-bar-vim-mode').addClass('status-bar-vim-mode-visual').html("Visual")
+
+  removeStatusBarClass: -> $('#status-bar-vim-mode').removeClass('status-bar-vim-mode-insert').removeClass('status-bar-vim-mode-command').removeClass('status-bar-vim-mode-visual')


js fix

js fix

--- app/assets/javascripts/build.coffee
+++ app/assets/javascripts/build.coffee
@@ -41,9 +41,9 @@
             dataType: "json"
             success: (build) =>
               if build.status == "running"
-                previous_build_trace_html = $('#build-trace code').html()
+                previous_build_trace_html = $('#build-trace code').html() || ''
                 build_trace_diff = build.trace_html.substr(previous_build_trace_html.length)
-                previous_results_html = $('#build-report .results').html()
+                previous_results_html = $('#build-report .results').html() || ''
                 build_result_diff = build.report_html.substr(previous_results_html.length)
                 $('#build-trace code').append(build_trace_diff)
                 $('#build-trace').append '<i class="icon-refresh icon-spin"/>' unless $('#build-trace').find('i.icon-spin')


Get onload working with turbo links.

Get onload working with turbo links.

--- app/assets/javascripts/games.coffee
+++ app/assets/javascripts/games.coffee
@@ -7,7 +7,8 @@
 NOT_TAKEN_OPACITY = 0
 NOT_TAKEN_HOVER_OPACITY = 0.5

-$(document).ready ->
+document.addEventListener "turbolinks:load", ->
+  console.log('ready')
   setupRoute = (routeDetails) ->
     route = $(routeDetails.svgId)
     route.attr('filter', 'url(#glow)')


Removing Dead Code

Removing Dead Code

--- app/components/analysis-view.coffee
+++ app/components/analysis-view.coffee
@@ -4,7 +4,6 @@
   classNames: ["col-md-12"]
   model: null
   showVulnerability: false
-  needs: ['application']

   showOrHide: (->
     if @get "showVulnerability"


opt code

opt code

--- light-promise.coffee
+++ light-promise.coffee
@@ -7,18 +7,18 @@
 isFun = (a) -> typeof a is 'function'
 isObj = (a) -> a and typeof a is 'object'

-if process?.nextTick then setTimeout = process.nextTick
-
-redStart = '\u001b[31m'
-redEnd = '\u001b[39m'
-
-unCaughtError = (promise) ->
-    setTimeout ->
-        if promise.state is STATE.REJECTED and not promise.onRejected.length
-            reason = promise.reason.stack or promise.reason
-            console.error redStart, "Possibly unhandled error: ", reason, redEnd
-            throw promise.reason
-    , 0
+$nextTick = if process?.nextTick then process.nextTick else setTimeout
+
+# redStart = '\u001b[31m'
+# redEnd = '\u001b[39m'
+
+# unCaughtError = (promise) ->
+#     $nextTick ->
+#         if promise.state is STATE.REJECTED and not promise.onRejected.length
+#             reason = promise.reason.stack or promise.reason
+#             console.error redStart, "Possibly unhandled error: ", reason, redEnd
+#             throw promise.reason
+#     , 0

 STATE =
     PENDING: 0
@@ -94,7 +94,7 @@
             @onFulfilled = []
             @onRejected = []
             @onFulfilled.num = @onRejected.num = 0
-            setTimeout =>
+            $nextTick =>
                 try
                     # unCaughtError @
                     resolver and resolver.call null, @resolve, @reject
@@ -137,7 +137,7 @@

         _fireResolve: (@value) =>
             @state = STATE.FULFILLED
-            setTimeout =>
+            $nextTick =>
                 next = null
                 _onFullfilled = [].concat @onFulfilled
                 if @onFulfilled.num
@@ -160,7 +160,7 @@
             , 0
         _fireReject: (@reason) =>
             @state = STATE.REJECTED
-            setTimeout =>
+            $nextTick =>
                 next = null
                 _onRejected = [].concat @onRejected
                 if @onRejected.num
@@ -182,13 +182,9 @@
             , 0

         @resolve: (value) ->
-            p = new Promise()
-            p.resolve value
-            p
+            new Promise().resolve(value)
         @reject: (reason) ->
-            p = new Promise()
-            p.reject reason
-            p
+            new Promise().reject(reason)
         @all: (promises) ->
             if not promises or promises.length is 0
                 return Promise.resolve []


better error handling

better error handling

--- looker_client.coffee
+++ looker_client.coffee
@@ -17,7 +17,10 @@
       else if response.statusCode == 200
         successCallback?(JSON.parse(body))
       else
-        errorCallback?(JSON.parse(body))
+        try
+          errorCallback?(JSON.parse(body))
+        catch
+          errorCallback({error: body})
     )

   get: (path, successCallback, errorCallback) ->


Fix inconsistencies between route param names and handlers

--- http.coffee
+++ http.coffee
@@ -91,7 +91,7 @@
 listCompetitionsByMembership = (req, res) ->
   @listMultiple(
     res,
-    @api.listCompetitionsByMembership(req.body.userId),
+    @api.listCompetitionsByMembership(req.params.userId),
     { typeName: 'competitions' }
   )

@@ -119,7 +119,7 @@
   .done()

 endCompetition = (req, res) ->
-  @api.endCompetition(req.body.competitionId)
+  @api.endCompetition(req.params.competitionId)
   .then((competition) ->
     res.json(200, competition)
     console.log "[HTTP] 200: ended competition: #{competition}"
@@ -133,7 +133,7 @@
   .done()

 joinCompetition = (req, res) ->
-  @api.joinCompetition(req.body.userId, req.body.competitionId)
+  @api.joinCompetition(req.body.userId, req.params.competitionId)
   .then((competitionMembership) ->
     res.json(201, competitionMembership)
     console.log("[HTTP] 201: joined competition: #{competitionMembership}")


Suppress the EventEmitter's warning for ObjectsRegistry.

It's possible that many objects subscribed to it.

--- browser/lib/objects-registry.coffee
+++ browser/lib/objects-registry.coffee
@@ -41,6 +41,8 @@

 class ObjectsRegistry extends EventEmitter
   constructor: ->
+    @setMaxListeners Number.MAX_VALUE
+
     # Objects in weak map will be not referenced (so we won't leak memory), and
     # every object created in browser will have a unique id in weak map.
     @objectsWeakMap = new IDWeakMap


Adding 'Toggle bookmark' as context menu item

--- menus/bookmarks.cson
+++ menus/bookmarks.cson
@@ -14,3 +14,8 @@
     ]
   }
 ]
+'context-menu': {
+  'atom-text-editor': [
+    { 'label': 'Toggle bookmark', 'command': 'bookmarks:toggle-bookmark' }
+  ]
+}


oops!

oops!

--- caboose-model/lib/connection.coffee
+++ caboose-model/lib/connection.coffee
@@ -41,6 +41,7 @@
   {
     protocol: protocol
     host: host
+    port: port
     hosts: hosts
     path: path
     user: user