do not look into node_modules for erroneous file paths

--- src/steroids/GruntDefaults.coffee
+++ src/steroids/GruntDefaults.coffee
@@ -7,7 +7,7 @@
   grunt.registerTask 'check-cordova-js-paths', "Checks for deprecated cordova.js paths in all HTML files", ->
     results = []
     grunt.file.recurse Paths.applicationDir, (abspath, rootdir, subdir, filename)->
-      if subdir?.indexOf("dist") != 0 and filename.indexOf(".html") > -1
+      if subdir?.indexOf("dist") != 0 and subdir?.indexOf("node_modules") != 0 and filename.indexOf(".html") > -1
         contents = grunt.file.read abspath
         if contents.indexOf("/appgyver/cordova.js") > -1
           subdir = subdir || ""


update undo tests

update undo tests

--- tests/scripts/unit/undo-manager.coffee
+++ tests/scripts/unit/undo-manager.coffee
@@ -1,36 +1,43 @@
 describe('Undo manager', ->
-  describe('computeUndo', ->
+  describe('undo/redo', ->
     tests =
       'insert':
-        original: new Tandem.Delta(0, 12, [new Tandem.InsertOp("The lazy fox")])
+        initial: new Tandem.Delta(0, 12, [new Tandem.InsertOp("The lazy fox")])
         change: new Tandem.Delta(12, 18, [new Tandem.RetainOp(0, 9), new Tandem.InsertOp('hairy '), new Tandem.RetainOp(9, 12)])
         expected: new Tandem.Delta(18, 12, [new Tandem.RetainOp(0, 9), new Tandem.RetainOp(15, 18)])
       'delete':
-        original: new Tandem.Delta(0, 12, [new Tandem.InsertOp("The lazy fox")])
+        initial: new Tandem.Delta(0, 12, [new Tandem.InsertOp("The lazy fox")])
         change: new Tandem.Delta(12, 7, [new Tandem.RetainOp(0, 4), new Tandem.RetainOp(9, 12)])
         expected: new Tandem.Delta(7, 12, [new Tandem.RetainOp(0, 4), new Tandem.InsertOp('lazy '), new Tandem.RetainOp(4, 7)])
       'insert and delete':
-        original: new Tandem.Delta(0, 12, [new Tandem.InsertOp("The lazy fox")])
+        initial: new Tandem.Delta(0, 12, [new Tandem.InsertOp("The lazy fox")])
         change: new Tandem.Delta(12, 13, [new Tandem.RetainOp(0, 4), new Tandem.InsertOp('hairy '), new Tandem.RetainOp(9, 12)])
         expected: new Tandem.Delta(13, 12, [new Tandem.RetainOp(0, 4), new Tandem.InsertOp('lazy '), new Tandem.RetainOp(10, 13)])
       'delete at end':
-        original: new Tandem.Delta(0, 12, [new Tandem.InsertOp("The lazy fox")])
+        initial: new Tandem.Delta(0, 12, [new Tandem.InsertOp("The lazy fox")])
         change: new Tandem.Delta(12, 9, [new Tandem.RetainOp(0, 9)])
         expected: new Tandem.Delta(9, 12, [new Tandem.RetainOp(0, 9), new Tandem.InsertOp('fox')])
       'append':
-        original: new Tandem.Delta(0, 8, [new Tandem.InsertOp("The lazy")])
+        initial: new Tandem.Delta(0, 8, [new Tandem.InsertOp("The lazy")])
         change: new Tandem.Delta(8, 12, [new Tandem.RetainOp(0, 8), new Tandem.InsertOp(' fox')])
         expected: new Tandem.Delta(12, 8, [new Tandem.RetainOp(0, 8)])
       'replace':
-        original: new Tandem.Delta(0, 3, [new Tandem.InsertOp("fox")])
+        initial: new Tandem.Delta(0, 3, [new Tandem.InsertOp("fox")])
         change: new Tandem.Delta(3, 4, [new Tandem.InsertOp("lazy")])
         expected: new Tandem.Delta(4, 3, [new Tandem.InsertOp("fox")])

+    undoTests = new Scribe.Test.EditorTest()
     _.each(tests, (test, name) ->
-      it(name, ->
-        undoDelta = Scribe.UndoManager.computeUndo(test.change, test.original)
-        expect(undoDelta.isEqual(test.expected)).to.be.true
+      undoTests.run(name,
+        initial: test.initial
+        expected: test.expected
+        fn: (editor) ->
+          editor.applyDelta(test.change)
+        checker: (editor) ->
+          editor.undoManager.undo()
+          expect(editor.getDelta()).to.deep.equal(test.initial)
+          editor.undoManager.redo()
       )
     )
   )


refactor: clean up Choropleth api

refactor: clean up Choropleth api

--- app/scripts/main.coffee
+++ app/scripts/main.coffee
@@ -79,7 +79,7 @@
     deferred.promise

-  .factory 'choropleth', (vsData)->
+  .factory 'Choropleth', (vsData)->

     ###
     * d3 Map Drawing
@@ -87,73 +87,87 @@
     * http://bl.ocks.org/mbostock/4060606 )
     ###

-    width = 200;
-    height = 200;
-
-    projection = d3.geo.mercator()
-    projection.scale(50000)
-    projection.center([-76.6167, 39.2833])
-    projection.translate([width/2, height/2])
-
-    path = d3.geo.path().projection(projection)
-
-    choropleth = (svg, vsProp)->
-      svg = d3.select(svg)
-      vsData.then (dataset)->
-        mapdata = dataset.topojson
-        vitalsigns = dataset.vitalsigns
-        # Todo: get this hardcoded property name out of here.
-        feature = mapdata.objects["CSA_NSA_Tracts"]
-
-        val = (d)->
-          parseFloat(d.get(vsProp))
-        domain = [
-          d3.min vitalsigns.values(), val
-          d3.max vitalsigns.values(), val
-        ]
-
+    class Choropleth
+      constructor: (svg, @feature, @indicator) ->
+        @svg = d3.select(svg)
+        @projection = d3.geo.mercator()
+          .scale(50000)
+          .center([-76.6167, 39.2833])
+          .translate([@width/2, @height/2])
+        @path = d3.geo.path().projection(@projection)
+
+      width: 200
+      height: 200
+
+      # parse numerical data from strings
+      # Todo: handle dollar signs, commas, etc.
+      parseValue: (val)->
+        parseFloat(val)
+
+      # property value accessor
+      value: (d) =>
+        @parseValue(@vitalsigns.get(d.id)?.get(@indicator))
+
+      # method to compute the domain for our color scale
+      domain: () =>
+        d3.extent @vitalsigns.values(), (d)=>@parseValue(d.get(@indicator))
+
+      # method to compute the range for our color scale
+      range: () =>
+        d3.range(9).map (i) -> "q#{i}-9"
+
+      data: (mapdata, vitalsigns) =>
+        @mapdata = mapdata
+        @vitalsigns = vitalsigns
+        @redraw()
+
+      redraw: () =>
+        feature = @mapdata.objects[@feature]
+
+        ###
+        Rebuild quantizer each time, since domain and range could have changed
+        (in typical d3 style, we'd just have the quantize function be accessible
+        to client code, but since we want our default domain to be set when the
+        data is provided, it works better to expose domain() as a method that
+        can be overridden, and just build quantize from that... I think...)
+        ###
         quantize = d3.scale.quantize()
-          .domain(domain)
-          .range(d3.range(9).map( (i) -> ("q" + i + "-9") ) )
-
-        svg.attr("width", width)
-          .attr("height", height)
-          .attr("data-property", vsProp)
-          .attr("data-min", domain[0])
-          .attr("data-max", domain[1])
-
-        val = (d)->
-          parseFloat(vitalsigns.get(d.id)?.get(vsProp)) ? 0
-
-        svg.selectAll(".community")
-          .data(topojson.feature(mapdata, feature).features)
+          .domain(@domain())
+          .range(@range())
+
+        @svg.attr("width", @width)
+          .attr("height", @height)
+
+        @svg.selectAll(".community")
+          .data(topojson.feature(@mapdata, feature).features)
           .enter()
           .append("path")
+          .attr("d", @path)
           .attr("data-community", (d)->d.id)
-          .attr("data-property", val)
-          .attr("class", (d)->
-            quantize(val(d))
+          .attr("class", (d)=>
+            quantize(@value(d))
           )
-          .attr("d", path)
-
-        svg.append("path")
-          .datum(topojson.mesh(mapdata, feature))
-          .attr("d", path)
+
+        @svg.append("path")
+          .datum(topojson.mesh(@mapdata, feature))
+          .attr("d", @path)
           .attr("class", "community-boundary");

-      , (reason) ->
-        console.error(reason)
-
-
-  .directive 'vsMap', (vsData, choropleth)->
+
+  .directive 'vsMap', (vsData, Choropleth)->
     templateUrl: "partials/vs-map.tpl.html"
     restrict: 'E'
     replace: true
+
     link: (scope, element, attr)->
+
       attr.$observe 'property', (prop)->
+        svgNode = element.children("svg")[0]
+        vsMap = new Choropleth(svgNode, "CSA_NSA_Tracts", prop)
+
         vsData.then (dataset) ->
-          choropleth(element.children("svg")[0], prop)
           scope.varInfo = dataset.varInfo.get(prop)
+          vsMap.data(dataset.topojson, dataset.vitalsigns)

   .factory 'Selection', ()->


Added storageKey

Added storageKey

--- core/app/backbone/views/react_feed_selection.coffee
+++ core/app/backbone/views/react_feed_selection.coffee
@@ -61,6 +61,7 @@
               ReactTextArea
                 ref: 'challengeDescription'
                 placeholder: 'Describe the challenge'
+                storageKey: 'createChallengeDescription'
               _button ["button-confirm", onClick: @_postChallenge],
                 "Create challenge"
         ]


Add E2E test cases for hormone receptor status.


Require ipfilter

Require ipfilter

--- src/lib/setup.coffee
+++ src/lib/setup.coffee
@@ -45,6 +45,7 @@
 AirbrakeClient = require 'airbrake-js'
 makeErrorHandler = require 'airbrake-js/dist/instrumentation/express'
 stylus = require 'stylus'
+{ IpFilter: ipfilter } = require "express-ipfilter"

 localsMiddleware = require './middleware/locals'
 ensureSSLMiddleware = require './middleware/ensure_ssl'


We do not need subdomain any more

We do not need subdomain any more

--- src/pagerduty.coffee
+++ src/pagerduty.coffee
@@ -22,9 +22,6 @@
     missingAnything = false
     unless pagerDutyFromEmail?
       msg.send "PagerDuty From is missing:  Ensure that HUBOT_PAGERDUTY_FROM_EMAIL is set."
-      missingAnything |= true
-    unless pagerDutySubdomain?
-      msg.send "PagerDuty Subdomain is missing:  Ensure that HUBOT_PAGERDUTY_SUBDOMAIN is set."
       missingAnything |= true
     unless pagerDutyApiKey?
       msg.send "PagerDuty API Key is missing:  Ensure that HUBOT_PAGERDUTY_API_KEY is set."


Package API outline

Package API outline

--- app/api/package.coffee
+++ app/api/package.coffee
@@ -5,3 +5,14 @@
 #      \/_/   \/_/ /_/   \/_/\/_/   \/_/ \/_/   \/_____/   \/_/     \/_/
 #
 # Copyright  2015 Tranzit Development Team
+angular.module 'Tranzit.api.package', ['Tranzit.config']
+
+# Authentication API service
+.service 'TranzitPackage', ($http, $localStorage, $q, ApiConfig, TranzitAuthSession) ->
+
+  # Keep a reference of @ in case we need it later in nested functions
+  self = @
+
+  # TODO - package server functions
+
+  return @


remove step from game object default events

--- src/timed_events.coffee
+++ src/timed_events.coffee
@@ -45,4 +45,4 @@

   sendOrApply: (fn) ->
     if typeof fn is "function"
-      +      fn.a


sending all events to particles

sending all events to particles

--- src/timed_events.coffee
+++ src/timed_events.coffee
@@ -10,8 +10,9 @@
 TimedEvents = (I={}) ->
   Object.reverseMerge I,
     everyEvents: []
+
+  self.bind "update", ->

-  self.bind ""

   ###*
   Execute fn every n frames.


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/timed_events.coffee
+++ src/timed_events.coffee
@@ -24,7 +24,7 @@
   @name every
   @methodOf TimedEvents#
   @param {Number} n The number of frames to wait before executing the callback
-  @param {Function} fn The code to execute after the number of frames h
+  @param {Function} fn The code to execute after the number of frames has passed
   ###
   every: (n, fn) ->
     if I.age.mod(n) is 0


levels and default game states

levels and default game states

--- src/title_screen.coffee
+++ src/title_screen.coffee
@@ -8,6 +8,7 @@
     self.centerText canvas, App.name

     self.centerText canvas, "Press any key to start",
-      size:
+      size: 12
+

   return self


getElementById fix

getElementById fix

--- src/quo.query.coffee
+++ src/quo.query.coffee
@@ -12,8 +12,8 @@
             elements = domain.getElementsByClassName selector.replace(".", "")
         else if TAG_SELECTOR.test(selector)
             elements = domain.getElementsByTagName(selector)
-        else if ID_SELECTOR.test(selector)
-            elements = document.getElementById selector.replace("#", "")
+        else if ID_SELECTOR.test(selector) and domain isnt document
+            elements = domain.getElementById selector.replace("#", "")
             unless elements then elements = []
         else
             elements = domain.querySelectorAll selector


fix: duplicate playlist when clicking an item

When a user clicked on a playlist entry, the player would load the first
10 entries again and append them to the playlist. This is no longer the
case.

--- src/javascripts/extensions/playlist.coffee
+++ src/javascripts/extensions/playlist.coffee
@@ -19,14 +19,20 @@

     return unless @app.podcast.hasEpisodes()

-    @playlistLoader = new PlaylistLoader(@app)
-    @playlistLoader.loadEpisodes().done =>
-      @episodes = @app.podcast.episodes
-      @renderPanel()
-      @renderButton()
+    if @app.podcast.episodes.length
+      @finishLoading()
+    else
+      @app.playlistLoader = new PlaylistLoader(@app)
+      @app.playlistLoader = new PlaylistLoader(@app)
+      @app.playlistLoader.loadEpisodes().done(@finishLoading)

-      @app.theme.addExtension(this)
-      @setCurrentEpisode()
+  finishLoading: () =>
+    @episodes = @app.podcast.episodes
+    @renderPanel()
+    @renderButton()
+
+    @app.theme.addExtension(this)
+    @setCurrentEpisode()

   defaultOptions:
     showOnStart: false
@@ -103,7 +109,7 @@
     @app.theme.updateView()

   loadMoreEpisodes: () =>
-    @playlistLoader.loadNextPage().done (data) =>
+    @app.playlistLoader.loadNextPage().done (data) =>
       if data.episodes.length == 0
         @panel.find('button.load-more').hide()
       else


uniques function now correctly checks inner arrays

--- src/util/uniques.coffee
+++ src/util/uniques.coffee
@@ -29,11 +29,11 @@
         vals.push v
         lookups.push l

-  if typeof fetch is "function"
+  if typeof fetch is "function" and vars
     for d in data
       val = uniques fetch(vars, d, value, depth)
-      val = val[0] if val.length is 1
-      check val
+      for v in val
+        check v
   else if typeof value is "function"
     for d in data
       val = value d


Deprecate all Selection::on calls

Deprecate all Selection::on calls

--- src/selection.coffee
+++ src/selection.coffee
@@ -67,6 +67,8 @@
         Grim.deprecate("Use Selection::onDidChangeRange instead. Call ::getScreenRange() yourself in your callback if you need the range.")
       when 'destroyed'
         Grim.deprecate("Use Selection::onDidDestroy instead.")
+      else
+        Grim.deprecate("Selection::on is deprecated. Use documented event subscription methods instead.")

     super


Blow up when triggering commands with a colon

--- src/space-pen.coffee
+++ src/space-pen.coffee
@@ -426,6 +426,21 @@
 $.Event.prototype.currentTargetView = -> $(@currentTarget).containingView()
 $.Event.prototype.targetView = -> $(@target).containingView()

+# Deprecations
+
+JQueryTrigger = $.fn.trigger
+$.fn.trigger = (eventName, data) ->
+  if typeof eventName is 'string' and eventName.indexOf(':') > -1
+    throw new Error """
+      `trigger` is no longer available for emitting events as it will not
+      correctly route the command to its handlers. Please use
+      `atom.commands.dispatch` instead. See the docs at
+      https://atom.io/docs/api/latest/CommandRegistry#instance-dispatch
+      for details.
+    """
+  else
+    JQueryTrigger.call(this, eventName, data)
+
 $.fn.setTooltip = ->
   throw new Error """
     setTooltip is no longer available. Please use `atom.tooltips.add` instead.


Optimize Admin test

Optimize Admin test

--- src/sys/admin.coffee
+++ src/sys/admin.coffee
@@ -1,11 +1,5 @@
 ###
 Test for Admin rights
 ###
-reg = GetObject "winmgmts://./root/default:StdRegProv"
-params = reg.Methods_ "EnumKey"
-  .InParameters.SpawnInstance_()
-params.hDefKey = 0x80000003     # HKEY_USERS
-params.sSubKeyName = "S-1-5-20"
-
-module.exports = !reg.ExecMethod_ "EnumKey", params
-    .ReturnValue
+module.exports = !GetObject "winmgmts://./root/default:StdRegProv"
+  .EnumKey 0x80000003, "S-1-5-20"


Change os-interactive to os-interactive-link for embedded sims

--- src/scripts/modules/media/body/body.coffee
+++ src/scripts/modules/media/body/body.coffee
@@ -45,7 +45,7 @@
         .solution > .ui-toggle-wrapper > .ui-toggle': 'toggleSolution'
       'keydown .media-body': 'checkKeySequence'
       'keyup .media-body': 'resetKeySequence'
-      'click .os-interactive': 'simLink'
+      'click .os-interactive-link': 'simLink'

     initialize: () ->
       super()
@@ -73,7 +73,7 @@
       try
         if @model.get('loaded') and @model.asPage()?.get('loaded') and @model.asPage()?.get('active')

-          if $temp.find('.os-interactive .os-embed')
+          if $temp.find('.os-interactive-link')
             @model.set('sims', true)

           # Remove the module title and abstract TODO: check if it is still necessary


formatting

formatting

--- src/util/spec.coffee
+++ src/util/spec.coffee
@@ -7,7 +7,6 @@

 MIXINS =
-

   _default: ->
     Mixin.from_obj


Fix invalid channel message

Fix invalid channel message

--- src/victorops.coffee
+++ src/victorops.coffee
@@ -43,6 +43,13 @@
       error.response = response
       throw error

+  invalidChannel = (msg) ->
+    channels = []
+    for team, key of teams
+      channel = robot.adapter.client.getChannelGroupOrDMByName team
+      channels.push " <\##{channel.id}|#{channel.name}>" if channel
+    return msg.reply "You can only page for oncall in one of the following project channels: #{channels}"
+
   lookupUser = (name) ->
     name = name.replace '.', ' ' if name?
     users = robot.brain.users()
@@ -84,7 +91,7 @@
     [ __, message ] = msg.match
     room = msg.message.room
     team = teams[room]
-    return msg.reply "You can only page for oncall in one of the following project channels:" + (" <\##{channel}>" for channel, team of teams) if not team
+    return invalidChannel msg unless team

     whoIsOncall team, (person) ->
       if person
@@ -110,7 +117,7 @@
   robot.respond /whois oncall/, (msg) ->
     room = msg.message.room
     team = teams[room]
-    return msg.reply "You can only see who is oncall in the following project channels:" + (" <\##{channel}>" for channel, team of teams) if not team
+    return invalidChannel msg unless team

     whoIsOncall team, (person) ->
       if person


d3 stacked bar: fixed margin

d3 stacked bar: fixed margin

--- app/assets/javascripts/d3/stacked_bar.coffee
+++ app/assets/javascripts/d3/stacked_bar.coffee
@@ -12,7 +12,7 @@
     draw: =>
       margins =
         top: 20
-        bottom: 200
+        bottom: 20
         left: 30
         right: 30


Bug fix: GA event logging on category click in search results

--- app/assets/javascripts/site_search.js.coffee
+++ app/assets/javascripts/site_search.js.coffee
@@ -40,7 +40,7 @@

   # Event tracking of details for selected link in the search results
   if $("section.site-search").length
-    $('section.site-search h2 a, section.site-search .ess-bestbets a, section.site-search ul.breadcrumb a, section.site-search-categories a').click (event) ->
+    $('section.site-search h2 a, section.site-search .ess-bestbets a, section.site-search ul.breadcrumb a, section.site-search .categories a').click (event) ->
       $a = $(this)
       if typeof gaDelayEvent is "function" then gaDelayEvent($a, event)
       link = $a.attr('href')
@@ -59,5 +59,5 @@
         _gaq.push(['_trackEvent', 'SearchClickEditorsChoice', GAAction,  GALabel])

       # Track clicks on editors choich in the results list
-      if $a.closest(".site-search-categories").length > 0
+      if $a.closest(".categories").length > 0
         _gaq.push(['_trackEvent', 'SearchClickCategory', GAAction,  GALabel])


See commit for jquery.keyframes.js

See commit for jquery.keyframes.js

Signed-off-by: = <=>

--- jquery.keyframes.coffee
+++ jquery.keyframes.coffee
@@ -91,7 +91,7 @@
     repeat: 1
     direction: "normal"
     fillMode: "forwards"
-    complete: null
+    complete: callback

   frameOptions = $.extend defaultsOptions, frameOptions


fix ie9 media viewing

fix ie9 media viewing

There were two problems:
  * IE9 cannot do $("<source type='blah'>").attr('type') for some
    reason, so now we're collecting the types separately.
  * IE9 has trouble with canPlayType(), so the HTML5 detection stuff
    in mejs was failing. This forces IE9 to just use the flash player.

Test plan:
 * Test that video and audio still works in all browsers.

Change-Id: Ia9f35e211f601424b244220edebf5d11a8c0bed2
Reviewed-on: https://gerrit.instructure.com/18034
QA-Review: Bryan Madsen <0a3ba1af216659500dd8f41e29f3979f42452870@instructure.com>
Reviewed-by: Bracken Mosbacker <48b846001bca4166dc3fb7711563afc17525921e@instructure.com>
Tested-by: Jenkins <d95b56ce41a2e1ac4cecdd398defd7414407cc08@instructure.com>

--- app/coffeescripts/jquery/mediaComment.coffee
+++ app/coffeescripts/jquery/mediaComment.coffee
@@ -6,6 +6,13 @@
   'vendor/mediaelement-and-player'
   'jquery'
 ], (I18n, _, pubsub, mejs, $) ->
+
+  isIE9 = () -> $("body").hasClass("ie9")
+  isMobileDevice = () ->
+    agent = navigator.userAgent.toLowerCase()
+    agent.match(/ip(hone|od|ad)/i) or agent.match(/android/i)
+
+  browserSupportsVideoInAudioTag = () -> isMobileDevice()

   VIDEO_WIDTH = 550
   VIDEO_HEIGHT = 448
@@ -22,16 +29,12 @@
   $.extend mejs.MepDefaults,
     # prefer flash player, as it works more consistently
     # for now, but allow fallback to html5 (like on mobile)
-    mode: 'auto_plugin'
+    # Force IE9 to always use the plugin, because it has
+    # problems with the canPlayType call.
+    mode: if isIE9() then 'shim' else 'auto_plugin'
     success: (mediaElement, domObject) ->
       if(mediaElement.pluginType == 'flash')
         mediaElement.play()
-
-  isMobileDevice = () ->
-    agent = navigator.userAgent.toLowerCase()
-    agent.match(/ip(hone|od|ad)/i) or agent.match(/android/i)
-
-  browserSupportsVideoInAudioTag = () -> isMobileDevice()

   # track events in google analytics
   mejs.MepDefaults.features.push('googleanalytics')
@@ -43,7 +46,8 @@
       tracks = _.map data.media_tracks, (track) ->
           languageName = mejs.language.codes[track.locale] || track.locale
           "<track kind='#{track.kind}' label='#{languageName}' src='#{track.url}' srclang='#{track.locale}' />"
-      dfd.resolve {sources, tracks, can_add_captions: data.can_add_captions}
+      types = _.map data.media_sources, (source) -> source.content_type
+      dfd.resolve {sources, tracks, types, can_add_captions: data.can_add_captions}
     dfd

   # After clicking an image to play the video, load the sources and tracks
@@ -55,12 +59,11 @@
     {sourcesAndTracks, mediaType, height, width} = options
     tag_type = if mediaType is 'video' then 'video' else 'audio'

-    sourceTypes = _.map sourcesAndTracks.sources, (source) -> $(source).attr('type')
     # A lot of our recorded audio is actually served up via video/mp4 or video/flv.
     # We need to trick the flash player into playing the video, but looking like
     # an audio player. (Not necessary on iOS/Android - they seem fine playing
     # an mp4 inside an audio tag.)
-    if mediaType is 'audio' and sourceTypes[0].match(/^video\//) and !browserSupportsVideoInAudioTag()
+    if mediaType is 'audio' and sourcesAndTracks.types[0].match(/^video\//) and !browserSupportsVideoInAudioTag()
       tag_type = 'video'
       options.mediaPlayerOptions.isVideo = false
       options.mediaPlayerOptions.videoHeight = 30


pass overrides to the applyOverrides fn

pass overrides to the applyOverrides fn

--- js/libs/ab-tests.coffee
+++ js/libs/ab-tests.coffee
@@ -30,7 +30,7 @@

     @setup_tests()

-    @apply_overrides()
+    @apply_overrides(overrides)

     @notify_mixpanel()

@@ -54,8 +54,8 @@

     @ab_tests(tests)

-  apply_overrides: () =>
-    for own name, value of @overrides
+  apply_overrides: (overrides) =>
+    for own name, value of overrides
       if @ab_tests()[name]
         console.log "Overriding A/B test '#{name}' with value '#{value}'"
         @ab_tests()[name](value)


Add GA ping for each map pan.

Add GA ping for each map pan.


set offset to 0 on filtering / sorting

set offset to 0 on filtering / sorting


remove dotfile

remove dotfile


disabled synchronization permanently

disabled synchronization permanently

--- server/synchronization.coffee
+++ server/synchronization.coffee
@@ -3,16 +3,7 @@
 Meteor.startup ->
   startSync = (doc) ->
     currentProject = doc.project
-    sync()
-
-  Settings.find {},
-    fields:
-      project: 1
-      etcd: 1
-      etcdBaseUrl: 1
-  .observe
-    added: startSync
-    changed: startSync
+    # sync()

 toApp = (node) ->
   [ignore..., keyBase, project, appName, version] = node.key?.split('/')


converted to class syntax

converted to class syntax

--- server/ws/comms.client.coffee
+++ server/ws/comms.client.coffee
@@ -1,66 +1,64 @@
+# BSON code is commented
 # const BSON = require('bson')
 # const bson = new BSON()
-# note commented out BSON code
 eventSystem = require('#/app/browser+node/eventSystem.js')
-# function BlobtoJSON(blob) {
-#   return new Promise(function(resolve, reject){
-#     var reader = new FileReader()
-#     reader.readAsArrayBuffer(blob)
-#     reader.on('loadend', function(){
-#       var data = bson.deserialize( ArrayBuffertoBuffer(reader.result) )
-#       resolve(data)
-#     })
-#   })
-# }
-# function ArrayBuffertoBuffer(ab) {
-#   var buf = new Buffer(ab.byteLength)
-#   var view = new Uint8Array(ab)
-#   for (var i = 0; i < buf.length; ++i) {
+
+# BlobtoJSON = (blob) ->
+#   new Promise (resolve, reject) ->
+#     reader = new FileReader
+#     reader.readAsArrayBuffer blob
+#     reader.on 'loadend', ->
+#       data = bson.deserialize(ArrayBuffertoBuffer(reader.result))
+#       resolve data
+
+# ArrayBuffertoBuffer = (ab) ->
+#   buf = new Buffer(ab.byteLength)
+#   view = new Uint8Array(ab)
+#   i = 0
+#   while i < buf.length
 #     buf[i] = view[i]
-#   }
-#   return buf
-# }
+#     ++i
+#   buf
+class ClientComms
+  constructor: (url) ->
+    # example url 'ws://localhost:3000'
+    @url = url
+    @e = eventSystem()
+    @ws = null
+    @reqs_made = {}
+    @connect()
+    @on = @e.on

-module.exports = (path) ->
-  e = eventSystem()
-  o = {}
+  connect: ->
+    @ws = new WebSocket(@url)

-  connect = ->
-    o.ws = new WebSocket(path)
-    # 'ws://localhost:3000'
-    o.ws.on 'open', ->
-      e.emit 'connection'
+    @ws.on 'open', ->
+      @e.emit 'connection'

-    o.ws.on 'close', ->
-      e.emit 'close'
+    @ws.on 'close', ->
+      @e.emit 'close'

-    o.ws.on 'message', (ws_event) ->
+    @ws.on 'message', (ws_event) ->
       data = JSON.parse(ws_event.data)
       if typeof data.req_res == 'string'
-        reqs_made[data.req_res].resolve data.data
-        delete reqs_made[ws_event.data.req_res_rand]
-
-      e.emit 'message', data
+        @reqs_made[data.req_res].resolve data.data
+        delete @reqs_made[ws_event.data.req_res_rand]
+      @e.emit 'message', data
       # if (ws_event.data.toString() !== '[object Blob]') {return}
       # BlobtoJSON(ws_event.data).then(function(data){
       #   e.emit('message', data)
       # })

-  o.reconnect = ->
-    if o.ws then o.ws.close()
-    connect()
+  reconnect: ->
+    if @ws then @ws.close()
+    @connect()

-  o.send = (data) ->
-    o.ws.send JSON.stringify(data)
+  send: (data) ->
+    @ws.send JSON.stringify(data)
     # o.ws.send( bson.serialize(data, {ignoreUndefined: false}) )
-    return

-  o.on = e.on
-  o.off = e.off
   # req res http pattern
-  reqs_made = {}
-
-  o.req = (data) ->
+  req: (data) ->
     new Promise (resolve, reject) ->
       rand = Date.now() + '-' + Math.random()
       reqs_made[rand] =
@@ -68,18 +66,13 @@
         resolve: resolve
         reject: reject
         res_data: undefined
-      o.send
+      @send
         req_res: rand
         data: data
-
-  connect()
-  o

+# api usage
 # comms.send
 # comms.on('connection')
 # comms.on('message')
 # comms.on('close')
-# comms.reconnect()
-
-# ---
-# generated by js2coffee 2.2.0+# comms.reconnect()


possibility to change locale on fly (for constructor)

--- app/assets/javascripts/uploadcare/locale.js.coffee
+++ app/assets/javascripts/uploadcare/locale.js.coffee
@@ -14,8 +14,7 @@
     translations: ns.translations[defaultLang]
     pluralize: ns.pluralize[defaultLang]

-  build = utils.once ->
-    settings = s.build()
+  _build = (settings) ->
     lang = settings.locale || defaults.lang
     translations = $.extend(true, {},
       ns.translations[lang],
@@ -28,6 +27,13 @@

     {lang, translations, pluralize}

+  build = utils.once ->
+    _build s.build()
+
+  # Backdoor for widget constructor
+  ns.rebuild = (settings) ->
+    result = _build s.build settings
+    build = -> result

   translate = (key, node) ->
     path = key.split('.')


Fix #3159

Fix #3159

--- app/views/play/level/modal/HeroVictoryModal.coffee
+++ app/views/play/level/modal/HeroVictoryModal.coffee
@@ -448,8 +448,6 @@
     navigationEvent = route: nextLevelLink, viewClass: viewClass, viewArgs: viewArgs
     if @level.get('slug') is 'lost-viking' and not (me.get('age') in ['0-13', '14-17'])
       @showOffer navigationEvent
-    else if @level.get('slug') is 'a-mayhem-of-munchkins' and not (me.get('age') in ['0-13']) and not options.showLeaderboard
-      @showOffer navigationEvent
     else
       Backbone.Mediator.publish 'router:navigate', navigationEvent


bugfix

bugfix

--- client.iced
+++ client.iced
@@ -140,7 +140,9 @@
           file.dest_path = path.join cwd, task.name, file.name.replace /[\/\\]/g, path.sep
         file.finished = false
         if file.url
-          await fs.stat file.dest_path, defer e, dest_stats
+          dest_stats = null
+          try
+            dest_stats = fs.statSync file.dest_path
           file.finished = dest_stats?.size == file.size
           if file.finished
             stats.retrieves = stats.retrieves.filter((r)-> r.task.id != task.id || r.file.name != file.name)


Normalize Range contents to eliminate empty text nodes

--- src/trix/utilities/dom_range_change.coffee
+++ src/trix/utilities/dom_range_change.coffee
@@ -15,11 +15,9 @@
     range.setStart(@getStartContainerAndOffset()...)
     range.setEnd(@getEndContainerAndOffset()...)

-    {firstChild, lastChild} = range.cloneContents()
-    firstChild = firstChild.firstChild while firstChild.firstChild
-    lastChild = lastChild.lastChild while lastChild.lastChild
-
-    firstChild.textContent is Trix.ZERO_WIDTH_SPACE and lastChild.textContent is Trix.ZERO_WIDTH_SPACE
+    contents = range.cloneContents()
+    contents.normalize()
+    contents.childNodes.length is 1 and contents.firstChild.textContent is Trix.ZERO_WIDTH_SPACE

   isntEditable: ->
     focusElement = DOM.findElementForContainerAtOffset(@getFocusContainerAndOffset()...)


Refactored setting of opinion on fact and fact_relation

--- core/app/backbone/views/fact_relations/vote_view.coffee
+++ core/app/backbone/views/fact_relations/vote_view.coffee
@@ -23,53 +23,29 @@
     believes_fact: => @believes_fact()

   save: ->
-    opinion = if @ui.fact_relation.is(':checked')
-                'believes'
-              else
-                'none'
-
-    @set_fact_relation_opinion opinion
-
-    believe = if @ui.fact.is(':checked')
-                'believe'
-              else
-                'none'
-
-    @set_fact_opinion believe
+    @set_fact_relation_opinion @ui.fact_relation.is(':checked')
+    @set_fact_opinion @ui.fact.is(':checked')

     @trigger 'saved'

   believes_fact_relation: -> @model.isBelieving()
-  believes_fact: ->
-    @model.getFact().get('fact_wheel').opinion_types.believe.is_user_opinion
+  believes_fact: -> @model.getFact().getFactWheel().isUserOpinion 'believe'

-  set_fact_relation_opinion: (new_opinion) ->
-    # TODO: Refactor - almost same as in FactRelationVoteDownVIew
-    old_opinion = @model.current_opinion()
+  set_fact_relation_opinion: (enable_opinion, opinion='believes') ->
+    is_current_opinion = @model.current_opinion() == opinion

-    if old_opinion == null || 'disbelieves'
-      if new_opinion == 'believes'
-        @model.setOpinion new_opinion
+    if enable_opinion
+      @model.setOpinion opinion unless is_current_opinion
+    else
+      @model.removeOpinion() if is_current_opinion

-    if old_opinion == 'believes'
-      if new_opinion == 'none'
-        @model.removeOpinion()
+  set_fact_opinion: (enable_opinion, opinion='believe') ->
+    is_current_opinion = @model.getFact().getFactWheel().isUserOpinion opinion

-  set_fact_opinion: (new_opinion) ->
-    # TODO: Refactor - almost same as in FactRelationVoteDownVIew
-    old_opinion = @model.getFact().getFactWheel().userOpinion()
-
-    if !old_opinion
-      if new_opinion == 'believe'
-        @_set_fact_opinion new_opinion
-
-    if old_opinion == 'believes'
-      if new_opinion == 'none'
-        @_unset_fact_opinion new_opinion
-
-    if old_opinion == 'doubt' || 'disbelieve'
-      if new_opinion == 'believe'
-        @_set_fact_opinion new_opinion
+    if enable_opinion
+      @_set_fact_opinion opinion unless is_current_opinion
+    else
+      @_unset_fact_opinion opinion if is_current_opinion

 class window.FactRelationVoteDownView extends FactRelationVoteView
   ui:
@@ -99,8 +75,7 @@
     @trigger 'saved'

   disbelieves_fact_relation: -> @model.isDisBelieving()
-  disbelieves_fact: ->
-    @model.getFact().get('fact_wheel').opinion_types.disbelieve.is_user_opinion
+  disbelieves_fact: -> @model.getFact().getFactWheel().isUserOpinion 'disbelieve'

   set_fact_relation_opinion: (new_opinion) ->
     # TODO: Refactor - almost same as in FactRelationVoteUpVIew


Collaboration: Separate set and listen methods

--- client/ide/workspace/panes/editorpane.coffee
+++ client/ide/workspace/panes/editorpane.coffee
@@ -22,6 +22,7 @@
     file.once 'fs.delete.finished', =>
       KD.getSingleton('appManager').tell 'IDE', 'handleFileDeleted', file

+    @once 'RealTimeManagerSet', @bound 'setContentFromCollaborativeString'
     @once 'RealTimeManagerSet', @bound 'listenCollaborativeStringChanges'

@@ -211,15 +212,19 @@
       @setLineWidgets row, column, origin

+  setContentFromCollaborativeString: ->
+
+    {path} = @getFile()
+
+    unless string = @rtm.getFromModel path
+      return @rtm.create 'string', path, @getContent()
+
+    @setContent string.getText(), no
+
+
   listenCollaborativeStringChanges: ->

-    filePath = @getFile().path
-    string   = @rtm.getFromModel filePath
-
-    unless string
-      return @rtm.create 'string', filePath, @getContent()
-
-    @setContent string.getText(), no
+    return  unless string = @rtm.getFromModel @getFile().path

     @rtm.bindRealtimeListeners string, 'string'

@@ -232,8 +237,10 @@

     string = @rtm.getFromModel @getFile().path

+    return  if @isChangedByMe change
+
+    string = @rtm.getFromModel @getFile().path
     return  unless changedString is string
-    return  if @isChangedByMe change

     @applyChange change


fix: check if header cell has null value

--- coffee/connector_base/tableau_helpers.coffee
+++ coffee/connector_base/tableau_helpers.coffee
@@ -11,13 +11,15 @@
 # We need to map the source column data type to tableau column
 # data type. This function tries to figure out the type based on
 # its value.
-guessDataType = (value)-> switch
-    when parseInt(value).toString() == value.toString() then INT
-    when parseFloat(value).toString() == value.toString() then FLOAT
-    when value == "true" || value == "false" || value == true || value == false then INT
-    when _.isString(value) then STRING
-    when isFinite(new Date(value).getTime()) then DATE
-    else STIRNG
+guessDataType = (value)->
+    return STRING unless value
+    switch
+      when parseInt(value).toString() == value.toString() then INT
+      when parseFloat(value).toString() == value.toString() then FLOAT
+      when value == "true" || value == "false" || value == true || value == false then INT
+      when _.isString(value) then STRING
+      when isFinite(new Date(value).getTime()) then DATE
+      else STIRNG

 getJsonType = (value)-> switch
   when _.isArray(value) then getJsonType(_.first(value))


Setup now uses yield.

Setup now uses yield.

--- server/src/scripts/setup/setup.coffee
+++ server/src/scripts/setup/setup.coffee
@@ -31,9 +31,9 @@

     del = ->
         (Q.async ->
-            db = yield Q.nfcall(database.getDb)
             utils.log 'Deleting main database.'
-            result = yield db.dropDatabase()
+            db = yield Q.nfcall database.getDb
+            result = yield Q.ninvoke(db, "dropDatabase")
             utils.log 'Everything is gone now.')()

     create = ->
@@ -93,22 +93,20 @@

     if '--delete' in process.argv
-        del().then ->
-            process.exit()
+        (Q.async ->
+            yield del()
+            process.exit())()
+
     else if '--create' in process.argv
-        utils.log 'Setup will begin in 3 seconds.'
-        fnCreate = ->
-            create().then ->
-                process.exit()
-        setTimeout fnCreate, 1000
+        (Q.async ->
+            yield create()
+            process.exit())()

     else if '--recreate' in process.argv
-        fnCreate = ->
-            create().then ->
-                process.exit()
-        del().then ->
-            utils.log 'Setup will begin in 3 seconds.'
-            setTimeout fnCreate, 1000
+        (Q.async ->
+            yield del()
+            yield create()
+            process.exit())()
     else
         utils.log 'Invalid option.'
         process.exit()
@@ -140,5 +138,5 @@
         req.write(data)

     req.end()
-
+
 init()


Fixed formatting issue in Firefox

Fixed formatting issue in Firefox

--- web/site/src/javascript/helpers.coffee
+++ web/site/src/javascript/helpers.coffee
@@ -39,10 +39,17 @@

 exports.floor_of = (sensor) -> sensor.geometry.coordinates[2]
 exports.id_of = (sensor) -> sensor.properties.id
-svg_px_size = (el, attr) -> +(el.style attr)[0..-3]
-exports.svg_px_size = svg_px_size
-exports.svg_px_width = (el) -> svg_px_size el, 'width'
-exports.svg_px_height = (el) -> svg_px_size el, 'height'
+svg_px_size_by_style = (el, attr) ->
+  size = +(el.style attr)[0..-3]
+  if size then size else 0
+svg_px_size_by_bbox = (el, attr) -> el.node().getBBox()[attr]
+exports.svg_px_size = svg_px_size_by_style
+exports.svg_px_width = (el) ->
+  size_by_style = svg_px_size_by_style el, 'width'
+  if size_by_style then size_by_style else svg_px_size_by_bbox el, 'width'
+exports.svg_px_height = (el) ->
+  size_by_style = svg_px_size_by_style el, 'height'
+  if size_by_style then size_by_style else svg_px_size_by_bbox el, 'height'
 exports.omit_keys = (keys, obj) ->
   new_obj = {}
   new_obj[k] = v for k, v of obj when k not in keys


When the attribute is constrainted with `AllSameValue` we simply use action `setAttributeInAllVariants`

This is pretty brute force as it will in set the sameValue attribute for each variant again.

--- src/coffee/utils/product-utils.coffee
+++ src/coffee/utils/product-utils.coffee
@@ -122,7 +122,7 @@
     # this will sort the actions ranked in asc order (first 'remove' then 'add')
     _.sortBy actions, (a)-> a.action is "addPrice"

-  actionsMapVariantAttributes = (attributes, variant) ->
+  actionsMapVariantAttributes = (attributes, variant, allSameValueAttributeNames) ->
     actions = []
     if attributes
       _.each attributes, (value, key) ->
@@ -135,7 +135,7 @@
           else
             # key is index of attribute
             index = key
-            setAction = buildSetAttributeAction(value.value, variant, index)
+            setAction = buildSetAttributeAction(value.value, variant, index, allSameValueAttributeNames)
             actions.push setAction if setAction
         else if key.match(/^\_\d$/g)
           if _.isArray value
@@ -148,27 +148,27 @@
             actions.push setAction if setAction
           else
             index = key.substring(1)
-            setAction = buildSetAttributeAction(value.value, variant, index)
+            setAction = buildSetAttributeAction(value.value, variant, index, allSameValueAttributeNames)
             actions.push setAction if setAction

     actions

   # we assume that the products have the same ProductType
   # TODO: validate ProductType between products
-  actionsMapAttributes: (diff, new_obj) ->
+  actionsMapAttributes: (diff, new_obj, allSameValueAttributeNames = []) ->
     actions = []
     # masterVariant
     masterVariant = diff.masterVariant
     if masterVariant
       attributes = masterVariant.attributes
-      mActions = actionsMapVariantAttributes attributes, new_obj.masterVariant
+      mActions = actionsMapVariantAttributes attributes, new_obj.masterVariant, allSameValueAttributeNames
       actions = actions.concat mActions

     # variants
     if diff.variants
       _.each diff.variants, (variant, i)->
         attributes = variant.attributes
-        vActions = actionsMapVariantAttributes attributes, new_obj.variants[i]
+        vActions = actionsMapVariantAttributes attributes, new_obj.variants[i], allSameValueAttributeNames
         actions = actions.concat vActions

     actions
@@ -218,13 +218,15 @@
       price: price
   action

-buildSetAttributeAction = (diffed_value, variant, index)->
+buildSetAttributeAction = (diffed_value, variant, index, allSameValueAttributeNames)->
   attribute = variant.attributes[index]
   if attribute
     action =
       action: "setAttribute"
       variantId: variant.id
       name: attribute.name
+    if _.contains(allSameValueAttributeNames, attribute.name)
+      action.action = 'setAttributeInAllVariants'
     if _.isArray(diffed_value)
       action.value = helper.getDeltaValue(diffed_value)
     else
@@ -256,10 +258,13 @@

   action

-buildNewSetAttributeAction = (id, el)->
+buildNewSetAttributeAction = (id, el, allSameValueAttributeNames)->
+  attributeName = el.name
   action =
     action: "setAttribute"
     variantId: id
-    name: el.name
+    name: attributeName
     value: el.value
-  action
+  if _.contains(allSameValueAttributeNames, attributeName)
+    action.action = 'setAttributeInAllVariants'
+  action


Refactor front matter parser, fail on syntax error (Closes #1)

--- parsers/front_matter_parser.coffee
+++ parsers/front_matter_parser.coffee
@@ -7,35 +7,35 @@
 App =
   failures: []

-requiredAttrs    = ['title', 'date', 'author']
-ignoredFileNames = ['README.md', 'index.md']
+FrontMatterParser =
+  requiredAttrs: ['title', 'date', 'author']

-isEmptyObject    = (obj) -> !Object.getOwnPropertyNames(obj).length
-fileIsIgnored    = (fileName) ->
-  if ignoredFileNames.indexOf(fileName) >= 0 then return true else return false
+  ignoredFileNames: ['README.md', 'index.md']

-keys = (obj) -> Object.getOwnPropertyNames(obj)
+  fileIsIgnored: (fileName) ->
+    if @ignoredFileNames.indexOf(fileName) >= 0 then return true else return false

-FrontMatterParser =
   parse: (file, failed) ->
     process.stdout.write "."
-    return if fileIsIgnored(file.name)
+    return if @fileIsIgnored(file.name)

     fileText = fs.readFileSync(file.fullPath, 'utf-8')
     try
       frontMatter.parse fileText
     catch err
-      console.log err.stack
-      return console.log "This occurred in #{file.fullPath}"
+      console.log err.stack.slice(0, 250)
+      console.log "This occurred in #{file.fullPath}"
+      failed = true
+      return failed

     output = frontMatter.parse fileText
     fileAttrs = _.keys output.attributes

-    attrDiff    = _.difference requiredAttrs, fileAttrs
-    missingAttr = _.intersection(attrDiff, requiredAttrs)
+    attrDiff    = _.difference @requiredAttrs, fileAttrs
+    missingAttr = _.intersection(attrDiff, @requiredAttrs)
     if missingAttr.length
       console.log "\nFile '#{file.fullPath}' did not contain required attributes in the front-matter.".red
-      console.log "Required attributres are #{requiredAttrs.join(', ')}. File was missing a value for the following attibute(s): #{missingAttr.join(', ')}.\n".red
+      console.log "Required attributres are #{@requiredAttrs.join(', ')}. File was missing a value for the following attibute(s): #{missingAttr.join(', ')}.\n".red
       App.failures.push file.fullPath

     failed = true if App.failures.length


PUBDEV-187 Fix control type for Key<Model> fields; Handle VecSpecifier field.

--- src/ext/components/model-input.coffee
+++ src/ext/components/model-input.coffee
@@ -142,13 +142,13 @@

 createControlFromParameter = (parameter) ->
   switch parameter.type
-    when 'enum', 'Key<Frame>', 'Key<Model>', 'string'
+    when 'enum', 'Key<Frame>', 'VecSpecifier'
       createDropdownControl parameter
     when 'string[]'
       createListControl parameter
     when 'boolean'
       createCheckboxControl parameter
-    when 'byte', 'short', 'int', 'long', 'float', 'double', 'byte[]', 'short[]', 'int[]', 'long[]', 'float[]', 'double[]'
+    when 'Key<Model>', 'byte', 'short', 'int', 'long', 'float', 'double', 'byte[]', 'short[]', 'int[]', 'long[]', 'float[]', 'double[]'
       createTextboxControl parameter
     else
       console.error 'Invalid field', JSON.stringify parameter, null, 2


Remove duplicate Time entry from column type dropdown PUBDEV-446

--- src/ext/components/parse-input.coffee
+++ src/ext/components/parse-input.coffee
@@ -57,7 +57,6 @@
   'Enum'
   'Time'
   'UUID'
-  'Time'
   'String'
   'Invalid'
 ]


Dashboard: add rel=noreferrer for browsers that support it.

--- dashboard/assets/javascripts/views.js.coffee
+++ dashboard/assets/javascripts/views.js.coffee
@@ -48,7 +48,7 @@
       {{#if view.isIgnoreEntry}}
         {{#with view.entry}}
           {{pattern}} omits
-          {{url}}
+          {{url}}
         {{/with}}
       {{else}}
         {{view.entry.text}}


DM explorer js: remove debug stmt

DM explorer js: remove debug stmt

--- src-client/data-model-explorer.coffee
+++ src-client/data-model-explorer.coffee
@@ -12,7 +12,6 @@
 	$('[data-infolis-type]').each(->
 		$this = $(this)
 		toMatch = $this.attr('data-infolis-type')
-		console.log toMatch
 		if toMatch and toMatch.match re
 			if panelClass
 				$this = $this.find("." + panelClass).first()


Always render

Always render

--- apps/user_profile/client/user_profile.coffee
+++ apps/user_profile/client/user_profile.coffee
@@ -34,7 +34,7 @@
         @articles.fetch()
       else
         @model.fetchPosts(success: ((@posts) =>))
-    ).then @render
+    ).always @render

   openWebsite: ->
     popup = window.open @model.get('website'), '_blank'


window.id is no longer available when window is closed

--- atom/browser/lib/guest-window-manager.coffee
+++ atom/browser/lib/guest-window-manager.coffee
@@ -39,11 +39,12 @@
   # When |embedder| is destroyed we should also destroy attached guest, and if
   # guest is closed by user then we should prevent |embedder| from double
   # closing guest.
+  guestId = guest.id
   closedByEmbedder = ->
     guest.removeListener 'closed', closedByUser
     guest.destroy()
   closedByUser = ->
-    embedder.send 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_CLOSED', guest.id
+    embedder.send 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_CLOSED', guestId
     embedder.removeListener 'render-view-deleted', closedByEmbedder
   embedder.once 'render-view-deleted', closedByEmbedder
   guest.once 'closed', closedByUser


remove direct call to envify transform

remove direct call to envify transform

--- plugin/browserify.coffee
+++ plugin/browserify.coffee
@@ -61,10 +61,6 @@

   for transformName, transformOptions of browserifyOptions.transforms
     browserify.transform transformName, transformOptions
-
-  # use the envify transform to replace instances of `process.env`
-  # with strings
-  browserify.transform envify getEnvifyOptions getDebug(), step

   # have browserify process the file and include all required modules.
   # we receive a readable stream as the result


error when tests fail to load

error when tests fail to load

--- test/phantom.coffee
+++ test/phantom.coffee
@@ -8,6 +8,10 @@
   page.evaluate ->
     window.mocha_callback = (failed) ->
       window.callPhantom failed
+
+page.onResourceError = (error) ->
+    console.log "tests failed to load: #{error.errorString}"
+    phantom.exit 3

 page.onCallback = (failed) ->
   if failed > 0


Add Config->deletePlugin() test

Add Config->deletePlugin() test

--- test/config-test.coffee
+++ test/config-test.coffee
@@ -154,5 +154,21 @@

         'plugin.opts has key "hello"': (plugins) ->
           assert.equal plugins[0].opts.hasOwnProperty('hello'), true
+  .addBatch
+    'Delete a plugin':
+      topic: ->
+        new config.Config
+
+      'when we call addPlugin() and then deletePlugin()':
+        topic: (config) ->
+          config.addHostname 'http://ie.com'
+          config.addPlugin 'http://ie.com', 'grammar-check', {}
+          config.deletePlugin 'http://ie.com', 'grammar-check'
+
+        'returns a list of plugins': (plugins) ->
+          assert.equal _.isArray(plugins), true
+
+        'returns a list with a no plugins': (plugins) ->
+          assert.equal plugins.length, 0

   .export(module)


Use moves in selection when tabbing in a text editor

--- keymaps/table-edit.cson
+++ keymaps/table-edit.cson
@@ -46,6 +46,6 @@
 'atom-table-editor atom-text-editor:not([mini])':
   'enter': 'core:confirm'
   'ctrl-enter': 'editor:newline'
-  'tab': 'table-edit:move-right'
-  'shift-tab': 'table-edit:move-left'
+  'tab': 'table-edit:move-right-in-selection'
+  'shift-tab': 'table-edit:move-left-in-selection'
   'ctrl-tab': 'editor:indent'


Updated zappa.js size.

Updated zappa.js size.

--- tests/assets.coffee
+++ tests/assets.coffee
@@ -123,7 +123,7 @@
     c = t.client(zapp.app)
     c.get '/zappa/zappa.js', (err, res) ->
       t.equal 'content-type', res.headers['content-type'], 'application/javascript'
-      t.equal 'length', res.headers['content-length'], '7037'
+      t.equal 'length', res.headers['content-length'], '7032'

   'zappa (automatic)': (t) ->
     t.expect 'content-type', 'length'
@@ -135,7 +135,7 @@
     c = t.client(zapp.app)
     c.get '/zappa/zappa.js', (err, res) ->
       t.equal 'content-type', res.headers['content-type'], 'application/javascript'
-      t.equal 'length', res.headers['content-length'], '7037'
+      t.equal 'length', res.headers['content-length'], '7032'

   minify: (t) ->
     t.expect 'zappa', 'client', 'shared', 'coffee', 'js'
@@ -150,7 +150,7 @@

     c = t.client(zapp.app)
     c.get '/zappa/zappa.js', (err, res) ->
-      t.equal 'zappa', res.headers['content-length'], '3258'
+      t.equal 'zappa', res.headers['content-length'], '3253'
     c.get '/client.js', (err, res) ->
       t.equal 'client', res.headers['content-length'], '42'
     c.get '/shared.js', (err, res) ->


Ensure the autocomplete has an input to work with

--- components/search_bar/view.coffee
+++ components/search_bar/view.coffee
@@ -14,6 +14,9 @@

     # Search takes a fair_id param specific to fairs. Doesn't work for other models
     { @mode, @restrictType, @$input, @fairId, @includePrivateResults } = options
+
+    @$input ?= @$('input')
+    throw new Error('Requires an input field') unless @$input?

     @search = new Search
       restrictType : @restrictType


Add some options to advanced new file

Add some options to advanced new file

--- config.cson
+++ config.cson
@@ -13,3 +13,6 @@
   react: {}
   autosave:
     enabled: true
+  "advanced-new-file":
+    showFilesInAutoComplete: true
+    suggestCurrentFilePath: true


Throw error on non-unique node IDs

Throw error on non-unique node IDs

--- lib/BehaviorTree.coffee
+++ lib/BehaviorTree.coffee
@@ -145,6 +145,9 @@

     unless @nodes[source]
       throw new Error "Unknown source #{source} for choice #{id}"
+
+    if @nodes[id]
+      throw new Error "Node #{id} already exists"

     @nodes[id] =
       id: id
@@ -254,7 +257,7 @@
         val.else (c, e) ->
           state.handleResult state.rejected, 0, c, e, onResult
           return state
-        return
+        return state
       # Straight-up value returned
       return if choice.state is State.ABORTED
       return if typeof val?.isComplete is 'function' and not val.isComplete()


removing tws

removing tws

--- client/Framework/components/split/splitview.coffee
+++ client/Framework/components/split/splitview.coffee
@@ -89,13 +89,13 @@

   # CREATE RESIZERS
   _createResizers:->
-
+
     @resizers = for i in [1...@sizes.length]
       @_createResizer i
     @_repositionResizers()

   _createResizer:(index)->
-
+
     @addSubView resizer = new KDSplitResizer
       cssClass : "kdsplitview-resizer #{@getOptions().type}"
       type     : @getOptions().type
@@ -226,7 +226,7 @@

   _resizePanels:->
-
+
     @_sanitizeSizes()

   _repositionPanels:->


error recovery: fix bug where Turn on VM callback wasn't being called

--- client/app/MainApp/VirtualizationController.coffee
+++ client/app/MainApp/VirtualizationController.coffee
@@ -50,9 +50,9 @@
     @_runWraper 'vm.start', vm, KD.utils.getTimedOutCallbackOne
       name      : "vm.start"
       timeout   : 5000
-      onSuccess : callback?
+      onSuccess : -> callback?()
       onTimeout : KD.utils.notifyAndEmailVMTurnOnFailureToSysAdmin vm, "timeout"
-      onResult  : callback?
+      onResult  : -> callback?()

   stop:(vm, callback)->
     @_runWraper 'vm.shutdown', vm, callback


Flux: Environment: Define new action types

--- client/app/lib/flux/environment/actiontypes.coffee
+++ client/app/lib/flux/environment/actiontypes.coffee
@@ -9,4 +9,8 @@

   'MACHINE_UPDATED'
   'STACK_UPDATED'
+
+  'INVITATION_ACCEPTED'
+  'COLLABORATION_INVITATION_REJECTED'
+  'SHARED_VM_INVITATION_REJECTED'
 }


Delete old file info data file

Delete old file info data file

--- lib/data/file-info.coffee
+++ lib/data/file-info.coffee
@@ -1,6 +0,0 @@
-module.exports =
-class FileInfo
-  constructor: (@fileName, @fileType) ->
-
-  isValid: ->
-    return @fileName? and @fileType?


add comments

add comments

--- test/integration.coffee
+++ test/integration.coffee
@@ -178,6 +178,7 @@
     test.equal p3, c.promisesAwaitingResolution.a

     Promise.all([p1, p2, p3]).then ([a1, a2, a3]) ->
+      # three pointers to the same object!
       test.equal 'object', typeof a1
       test.equal a1, a2
       test.equal a2, a3
@@ -236,6 +237,7 @@
     test.ok not c.promisesAwaitingResolution?.a?

     Promise.all([p1, p2, p3]).then ([a1, a2, a3]) ->
+      # three different objects!
       test.equal 'object', typeof a1
       test.notEqual a1, a2
       test.notEqual a2, a3


fix fair spec

fix fair spec

--- test/models/fair.coffee
+++ test/models/fair.coffee
@@ -40,10 +40,9 @@
       _.last(Backbone.sync.args)[2].success new Backbone.Model fabricate 'profile'
       urls = (_.result(call[1], 'url') or call[2].url for call in Backbone.sync.args)
       urls[0].should.match /// api/v1/fair/.* ///
-      urls[1].should.match /// api/v1/search/filtered/fair/.*/suggest ///
-      urls[2].should.match /// api/v1/fair/.*/sections ///
-      urls[3].should.match /// api/v1/fair/.*/partners ///
-      urls[4].should.match /// api/v1/fair/.*/artists ///
+      urls[1].should.match /// api/v1/fair/.*/sections ///
+      urls[2].should.match /// api/v1/fair/.*/partners ///
+      urls[3].should.match /// api/v1/fair/.*/artists ///

   describe '#itemsToColumns', ->


Don't need fat arrow here

Don't need fat arrow here

--- lib/linter-liferay.coffee
+++ lib/linter-liferay.coffee
@@ -37,7 +37,7 @@
 				@lint()

-	lint: =>
+	lint: ->
 		new Promise (resolve, reject) =>
 			filePath = @editor.getPath()
 			output = []


remove redundant function

remove redundant function

--- lib/block-cursor.coffee
+++ lib/block-cursor.coffee
@@ -1,16 +1,5 @@
 'use strict'
 PackageConfigObserver = require 'atom-package-config-observer'
-
-equals = (obj, ref) ->
-  if obj is ref
-    return true
-  for own key, val of obj
-    if typeof val is 'object'
-      if not equals ref[key], val
-        return false
-    else if ref[key] isnt val
-      return false
-  return true

 class BlockCursor
   config:


Adding a failing indentation test.

Adding a failing indentation test.

--- test/test_indent.coffee
+++ test/test_indent.coffee
@@ -116,4 +116,21 @@
             errors = coffeelint.lint(source)
             assert.isEmpty(errors)

+    'Indentation with chained blocks' :
+
+        topic : """
+            counter
+               .tick ->
+                 3
+               .tick ->
+                 2
+               .tick ->
+                 1
+            """
+
+        'works' : (source) ->
+            errors = coffeelint.lint(source)
+            # FIXME: uncomment failing test
+            #assert.isEmpty(errors)
+
 }).export(module)


add code chunk support in pandoc

add code chunk support in pandoc

--- lib/pandoc-wrapper.coffee
+++ lib/pandoc-wrapper.coffee
@@ -6,6 +6,7 @@
 async = require 'async'
 Viz = require '../dependencies/viz/viz.js'
 plantumlAPI = require './puml'
+codeChunkAPI = require './code-chunk'
 {svgAsPngUri} = require '../dependencies/save-svg-as-png/save-svg-as-png.js'

 getFileExtension = (documentType)->
@@ -221,7 +222,7 @@

                 saveSvgAsPng svgElement, dest, {}, (error)->
                   document.body.removeChild(div)
-                  cb(null, {dest, start, end, content})
+                  cb(null, {dest, start, end, content, type: 'graph'})
             else
               cb(null, null)

@@ -241,7 +242,7 @@
             height = svgElement.getBBox().height

             saveSvgAsPng svgElement, dest, {width, height}, (error)->
-              cb(null, {dest, start, end, content})
+              cb(null, {dest, start, end, content, type: 'graph'})

         asyncFunc = helper(start, end, content)
@@ -263,31 +264,94 @@
               width = svgElement.getBBox().width
               height = svgElement.getBBox().height

-              saveSvgAsPng div.children[0], dest, {width, height}, (error)->
-                cb(null, {dest, start, end, content})
+              saveSvgAsPng svgElement, dest, {width, height}, (error)->
+                cb(null, {dest, start, end, content, type: 'graph'})

         asyncFunc = helper(start, end, content)
         asyncFunctions.push asyncFunc
     else # code chunk
          # TODO: support this in the future
-      null
-
-  async.parallel asyncFunctions, (error, data)->
+      helper = (start, end, content)->
+        (cb)->
+          def = lines[start].trim().slice(3)
+          match = def.match(/^\{\s*(\"[^\"]*\"|[^\s]*|[^}]*)(.*)}$/)
+
+          cmd = match[1].trim()
+          cmd = cmd.slice(1, cmd.length-1).trim() if cmd[0] == '"'
+          dataArgs = match[2].trim()
+
+          options = null
+          try
+            options = JSON.parse '{'+dataArgs.replace((/([(\w)|(\-)]+)(:)/g), "\"$1\"$2").replace((/'/g), "\"")+'}'
+          catch error
+            atom.notifications.addError('Invalid options', detail: dataArgs)
+            return
+
+          codeChunkAPI.run content, rootDirectoryPath, cmd, options, (error, data, options)->
+            outputType = options.output || 'text'
+
+            if outputType == 'text'
+              cb(null, {start, end, content, type: 'code_chunk', hide: options.hide, data, cmd})
+            else if outputType == 'none'
+              cb(null, {start, end, content, type: 'code_chunk', hide: options.hide, cmd})
+            else if outputType == 'html'
+              div = document.createElement('div')
+              div.innerHTML = data
+              if div.children[0].tagName.toLowerCase() == 'svg'
+                dest = path.resolve(rootDirectoryPath, (Math.random().toString(36).substr(2, 9)) + '_cc.png')
+
+                svgElement = div.children[0]
+                width = svgElement.getBBox().width
+                height = svgElement.getBBox().height
+                saveSvgAsPng svgElement, dest, {width, height}, (error)->
+                  cb(null, {start, end, content, type: 'code_chunk', hide: options.hide, dest, cmd})
+              else
+                cb(null, {start, end, content, type: 'code_chunk', hide: options.hide, data, cmd})
+            else
+              cb(null, null)
+
+      asyncFunc = helper(start, end, content)
+      asyncFunctions.push asyncFunc
+
+  async.parallel asyncFunctions, (error, dataArray)->
     # TODO: deal with error in the future.
     #
     imagePaths = []

-    for d in data
+    for d in dataArray
       continue if !d
-      {start, end, dest} = d
-      imgMd = "![](#{path.relative(rootDirectoryPath, dest)})"
-      imagePaths.push dest
-
-      lines[start] = imgMd
-      i = start + 1
-      while i <= end
-        lines[i] = null # filter out later.
-        i += 1
+      {start, end, type} = d
+      if type == 'graph'
+        {dest} = d
+        imgMd = "![](#{path.relative(rootDirectoryPath, dest)})"
+        imagePaths.push dest
+
+        lines[start] = imgMd
+
+        i = start + 1
+        while i <= end
+          lines[i] = null # filter out later.
+          i += 1
+      else # code chunk
+        {hide, data, dest, cmd} = d
+        if hide
+          i = start
+          while i <= end
+            lines[i] = null
+            i += 1
+          lines[end] = ''
+        else # replace ```{python} to ```python
+          line = lines[start]
+          i = line.indexOf('```')
+          lines[start] = line.slice(0, i+3) + cmd
+
+        if dest
+          imagePaths.push dest
+          imgMd = "![](#{path.relative(rootDirectoryPath, dest)})"
+          lines[end] += ('\n' + imgMd)
+
+        if data
+          lines[end] += ('\n' + data)

     lines = lines.filter (line)-> line!=null
               .join('\n')


Update -> re-add all() clause to product fetch query as required.

--- lib/product-import.coffee
+++ lib/product-import.coffee
@@ -106,6 +106,7 @@
         @client.productProjections
         .where(predicate)
         .staged(true)
+        .all()
         .fetch()
       .then (results) =>
         debug 'Fetched products: %j', results


basic scrolling utils

basic scrolling utils

--- lib/console/view.coffee
+++ lib/console/view.coffee
@@ -114,3 +114,18 @@

   updateLoading: ->
     if document.querySelector('.divider.loading')? then @loading true
+
+  scrollEndValue: ->
+    @items.querySelector('.divider:last-child').offsetTop -
+      @element.querySelector('.items-scroll').clientHeight + 5
+
+  isVisible: (pane, view) ->
+    [top, bottom] = [pane.scrollTop, bottom = pane.scrollTop + pane.clientHeight]
+    [ptop, pbottom] = [view.offsetTop, view.offsetTop + view.clientHeight]
+    bottom >= ptop >= top || bottom >= pbottom >= top
+
+  last: (xs) -> xs[xs.length-1]
+
+  shouldScroll: ->
+    @isVisible @element.querySelector('.items-scroll'),
+               @last @items.querySelectorAll('.cell')


Close the EnterDialog on ESC.

Close the EnterDialog on ESC.

--- lib/enter-dialog.coffee
+++ lib/enter-dialog.coffee
@@ -20,6 +20,8 @@
   initialize: ->
     @traceEditor.focus()

+    @on 'core:cancel', => @cancel()
+
   traceIt: ->
     atom.emit 'stacktrace:accept-trace', trace: @traceEditor.getText()
     @remove()


Refined debug function

Refined debug function

--- lib/flash-loader.coffee
+++ lib/flash-loader.coffee
@@ -10,10 +10,14 @@
 debug = (b = true, logFunc, errFunc) ->
   debugMode = b
   if debugMode
-    log = logFunc
-    log ?= console.log.bind console
-    error = errFunc
-    error ?= console.error.bind console
+    if typeof logFunc is 'function'
+      log = error = logFunc
+    else
+      log = console.log.bind console
+    if typeof errFunc is 'function'
+      error = logFunc
+    else
+      error = console.error.bind console
     log 'Debugging Flash Loader'
   else
     log = error = doNothing


some stats

some stats

--- lib/julia-client.coffee
+++ lib/julia-client.coffee
@@ -1,4 +1,5 @@
 {CompositeDisposable} = require 'atom'
+http = require 'http'
 terminal = require './connection/terminal'
 comm = require './connection/comm'
 process = require './connection/process'
@@ -49,6 +50,10 @@
       notifications.show("Client Connected")
     @withInk =>
       cons.activate()
+
+    try
+      if id = atom.config.get 'metrics.userId'
+        http.get "http://mikeinn.es/hit?id=#{id}&app=atom-julia"

   deactivate: ->
     @subscriptions.dispose()


coffee style

coffee style

--- lib/minimap-view.coffee
+++ lib/minimap-view.coffee
@@ -14,6 +14,7 @@
 class MinimapView extends View
   @content: ->
     @div class: 'minimap', =>
+      @subview 'miniEditorView', new MinimapEditorView()
       @div outlet: 'miniOverlayer', class: "minimap-overlayer"

   configs: {}
@@ -24,8 +25,8 @@
   initialize: ->
     @attach()

-    @on('mousewheel', @mouseWheel.bind(this))
-    @on('mousedown', @mouseDown.bind(this))
+    @on 'mousewheel', @mouseWheel.bind(this)
+    @on 'mousedown', @mouseDown.bind(this)

     @subscribe @paneView.model.$activeItem, @onActiveItemChanged
     @subscribe @paneView.model, 'destroy', => @destroy()
@@ -67,9 +68,7 @@
   # wtf? Long long function!
   updateMinimapView: ->
     unless @paneView.find('.minimap').length
-      @miniEditorView = new MinimapEditorView()
       @miniScrollView = @miniEditorView.find('.scroll-view')
-      @miniOverlayer.before(@miniEditorView)
       @paneView.append(this)

     if !@editor


remove triggerHandler

remove triggerHandler

--- lib/minimap-view.coffee
+++ lib/minimap-view.coffee
@@ -39,8 +39,7 @@
     @on 'mousedown', '.minimap-visible-area', (e) =>
       isPressed = true
       @on 'mousemove.visible-area', (e) =>
-        if isPressed
-          @.triggerHandler 'mousedown', e
+          @onMouseDown e if isPressed
       @on 'mouseup.visible-area', (e) =>
         isPressed = false
         @off '.visible-area'
@@ -181,16 +180,14 @@
     if wheelDeltaY
       @editorView.scrollTop(@editorView.scrollTop() - wheelDeltaY)

-  onMouseDown: (e, te) =>
+  onMouseDown: (e) =>
     @isClicked = true
     e.preventDefault()
     e.stopPropagation()
     minimapHeight = @miniScrollView.outerHeight()
     miniVisibleAreaHeight = @miniVisibleArea.height()
-    # `te` is a extraParameter of triggerHandler
-    pageY = e.pageY || (te && te.pageY)
     # Overlayer's center-y
-    y = pageY - @offset().top
+    y = e.pageY - @offset().top
     y = y - @minimapScroll * @scaleY
     n = y / @scaleY
     top = n - miniVisibleAreaHeight / 2


Fixes invalid minimap position

Fixes invalid minimap position

Activating the minimap on a non editor view and then switching to a
real editor wasn’t setting the minimap position at all.

--- lib/minimap-view.coffee
+++ lib/minimap-view.coffee
@@ -163,6 +163,7 @@
       @activatePaneViewMinimap() unless @minimapIsAttached()
       @storeActiveEditor()
       @setMinimapEditorView()
+      @updateMinimapView()
     else
       # Ignore any tab that is not an editor
       @deactivatePaneViewMinimap()


[cleanViewHelper] Add the processing of metadatas into the model processing.

--- lib/models/model.coffee
+++ lib/models/model.coffee
@@ -11,6 +11,7 @@
       options = options or {}
       super options
       @modelName = options.modelName if options.modelName?
+      @processMetadatas()
       if (this.has('id') and this.get('id') is 'new')
         this.unset('id', {silent: true})
         this.set('isNewModel', true, {silent: true})
@@ -21,7 +22,8 @@
         this.set(@defaultIfNew, { silent: true })
     # Process all the models metadatas, save theminto the model.
     processMetadatas: ->
-      this.metadats = metadaBuilder.getMetadatas(_.pick(this, "modelName", "metadatas"))
+      this.metadatas = metadaBuilder.getMetadatas(_.pick(this, "modelName", "metadatas"))
+      this.idAttribute = metadatas.idAttribute() # Set the id attribute depending ont the metadatas.
     #Define a method in order to be able to quickly remove errors form the model.
     unsetErrors:(options) ->
       options = options or {}


remove unnecessary code

remove unnecessary code

--- lib/path-scanner.coffee
+++ lib/path-scanner.coffee
@@ -29,24 +29,22 @@
       while fileCount--
         file = files.shift()
         filename = prefix + file
-        @processFile(filePath, filename)
+        @processFile(filename)

       @asyncCallDone()

-  processFile: (parentPath, filePath) ->
-    stat = @stat(parentPath, filePath)
+  processFile: (filePath) ->
+    stat = @stat(filePath)
     return unless stat

     if stat.isFile() and @pathFilter.isFileAccepted(path.relative(@rootPath, filePath))
       @stats[filePath] = stat
       @paths.push(filePath) unless _.contains(@paths, filePath)
-      @structure[parentPath] ?= []
-      @structure[parentPath].push(filePath) unless _.contains(@structure[parentPath], filePath)
       @emit('path-found', filePath)
     else if stat.isDirectory() and @pathFilter.isDirectoryAccepted(path.relative(@rootPath, filePath))
       @readDir(filePath)

-  stat: (parentPath, filePath) ->
+  stat: (filePath) ->
     return @stats[filePath] if @stats[filePath]?

     # lstat is SLOW, but what other way to determine if something is a directory or file ?


Remove logging

Remove logging

--- lib/tab-bar-view.coffee
+++ lib/tab-bar-view.coffee
@@ -158,7 +158,6 @@
       event.originalEvent.dataTransfer.setData 'text/plain', item.getPath()

       if item.isModified?() and item.getText?
-        console.log 'setting'
         event.originalEvent.dataTransfer.setData 'has-unsaved-changes', 'true'
         event.originalEvent.dataTransfer.setData 'modified-text', item.getText()


Guard against missing item

Guard against missing item

Closes #103

--- lib/tab-bar-view.coffee
+++ lib/tab-bar-view.coffee
@@ -183,8 +183,8 @@
     event.originalEvent.dataTransfer.setData 'from-process-id', @getProcessId()
     event.originalEvent.dataTransfer.setData 'from-routing-id', @getRoutingId()

-
     item = @pane.getItems()[element.index()]
+    return unless item?

     if typeof item.getUri is 'function' or typeof item.getPath is 'function'
       itemUri = item.getUri?() ? item.getPath?() ? ''


updated httpbind

updated httpbind

--- core/core_modules/httpbind.coffee
+++ core/core_modules/httpbind.coffee
@@ -32,6 +32,32 @@
 class HttpBind
     constructor : (@mew)->
         @staticPathDefinitionPool = {}
+
+    handleHttpAuth : (req,res,possibleAuthPool,next) ->
+        authorization = req.headers.authorization
+        if req.user
+            next()
+        else
+            unless authorization
+                res.statusCode = 401
+                res.setHeader('WWW-Authenticate', 'Basic realm="Authorization Required"')
+                return res.end('Unauthorized')
+            parts = authorization.split(' ')
+            if parts.length isnt 2
+                return res.send 400
+            scheme = parts[0]
+            credentials = new Buffer(parts[1], 'base64').toString()
+            index = credentials.indexOf(':')
+            if 'Basic' isnt scheme || index < 0
+                return res.send 400
+            user = credentials.slice(0, index)
+            pass = credentials.slice(index + 1)
+            if possibleAuthPool[user] and possibleAuthPool[user] is pass
+                next()
+            else
+                res.statusCode = 401
+                res.setHeader('WWW-Authenticate', 'Basic realm="Authorization Required"')
+                return res.end('Unauthorized')

     bindStatic : (context,location)->
         if location and context


bug fix, @options needed (instead of options)

--- core/app/backbone/views/auto_completes_view.coffee
+++ core/app/backbone/views/auto_completes_view.coffee
@@ -88,6 +88,6 @@
   hideAddNewIfNotNeeded: (channel)->
     if ( channel.get('user_channel') )
       lowerCaseTitle = channel.get('user_channel').title.toLowerCase();
-      lowerCaseSearch = options.mainView._lastKnownSearchValue.toLowerCase();
+      lowerCaseSearch = @options.mainView._lastKnownSearchValue.toLowerCase();

-      options.mainView.hideAddNew() if lowerCaseSearch == lowerCaseTitle
+      @options.mainView.hideAddNew() if lowerCaseSearch == lowerCaseTitle


Fix template functions argument completion

--- lib/dcd-provider.coffee
+++ lib/dcd-provider.coffee
@@ -142,7 +142,9 @@
             )

         when "calltips"
-          args = line.slice(line.lastIndexOf("(") + 1, line.length - 1).split(", ")
+          template = line.match(/[^)]*\)\s*\(/)
+          start = template[0].length - 1 if template
+          args = line.slice(line.indexOf("(", start) + 1, line.length - 1).split(", ")

           for i in [0 .. args.length - 1]
             args[i] = "${#{i + 1}:#{args[i]}}"


:bug: Fix another typo

:bug: Fix another typo

--- lib/linter-views.coffee
+++ lib/linter-views.coffee
@@ -88,7 +88,7 @@
     @_removeDecorations()
     @_panelWorkspace.destroy()
     @_removeBubble()
-    for statusTile in @_statusTiles.push
+    for statusTile in @_statusTiles
       statusTile.destroy()

   _changeTab: (Tab) ->


add `on` and `emit` feature.

add `on` and `emit` feature.

--- lib/messageProxy.coffee
+++ lib/messageProxy.coffee
@@ -1,12 +1,17 @@
 do (window) ->
   messageProxy =
-    on: (name, source, listener) ->
+    on: (sources, listener) ->
+      sources = [ sources ]  unless _isArray(sources)
+      window.addEventListener "message", (event) ->
+        if event.origin in sources
+          listener(event.data)
+      , false

-    emit: (name, target, args) ->
-
-    destroy: (name) ->
-
-    _listeners: {}
-
+    emit: (targets, message) ->
+      targets = [ targets ]  unless _isArray(targets)
+      window.postMessage message, target for target in targets
+
+  _isArray = (obj) ->
+    Object.prototype.toString.call(obj) is '[object Array]'

   window.messageProxy = window.messageProxy || messageProxy


fix(lib/options-abao.coffee): Fix for previous commit

Last commit renamed the file, but left out changes to remove Mocha options.

--- lib/options-abao.coffee
+++ lib/options-abao.coffee
@@ -1,16 +1,11 @@
 ###*
-# @file Command line options
+# @file Command line options (Abao-related)
 ###

 module.exports =
   'generate-hooks':
     description: 'Output hooks generated from template file and exit'
     type: 'boolean'
-
-  grep:
-    alias: 'g'
-    description: 'Only run tests matching <pattern>'
-    type: 'string'

   header:
     alias: 'h'
@@ -30,24 +25,9 @@
     description: 'Run test only if defined either before or after hooks'
     type: 'boolean'

-  invert:
-    alias: 'i'
-    description: 'Invert --grep matches'
-    type: 'boolean'
-
   names:
     alias: 'n'
     description: 'List names of requests and exit'
-    type: 'boolean'
-
-  reporter:
-    alias: 'R'
-    description: 'Specify reporter to use'
-    type: 'string'
-    default: 'spec'
-
-  reporters:
-    description: 'Display available reporters and exit'
     type: 'boolean'

   schemas:
@@ -71,9 +51,3 @@
     type: 'string'
     normalize: true

-  timeout:
-    alias: 't'
-    description: 'Set test case timeout in milliseconds'
-    type: 'number'
-    default: 2000
-


Update lib/package/menu.coffee

Update lib/package/menu.coffee

Co-Authored-By: Sebastian Pfitzner <4fbb502effede286ee1954eb0a60979288accf83@physik.hu-berlin.de>

--- lib/package/menu.coffee
+++ lib/package/menu.coffee
@@ -36,7 +36,7 @@
         label: 'Working Directory'
         submenu: [
           {label: 'Current File\'s Folder', command: 'julia-client:work-in-current-folder'}
-          {label: 'Current Project\'s Folders', command: 'julia-client:work-in-project-folder'}
+          {label: 'Select Project Folder', command: 'julia-client:work-in-project-folder'}
           {label: 'Home Folder', command: 'julia-client:work-in-home-folder'}
           {label: 'Select...', command: 'julia-client:select-working-folder'}
         ]


Fixes #62. Save and restore Preview scroll position between updates

--- lib/preview-view.coffee
+++ lib/preview-view.coffee
@@ -155,6 +155,8 @@
     if cEditor?
       # Source Code text
       text = cEditor.getText()
+      # Save Preview's Scroll position
+      spos = editor.getScrollTop()
       # console.log(text)
       # console.log(cEditor is editor, cEditor, editor)
       @showLoading()
@@ -182,6 +184,8 @@
           grammar = atom.syntax.selectGrammar("source.#{outLang}", result)
           editor.setGrammar grammar
           editor.setText result
+          # Restore Preview's Scroll Positon
+          editor.setScrollTop(spos)
           @hideViewPreview()
           focusOnEditor()
         # Check if result is a Space-pen View (jQuery)


getActiveEditor -> getActiveTextEditor in preview-view.coffee

--- lib/preview-view.coffee
+++ lib/preview-view.coffee
@@ -97,7 +97,7 @@
       @emitter.on 'did-change-title', callback

   handleEvents: () ->
-    currEditor = atom.workspace.getActiveEditor()
+    currEditor = atom.workspace.getActiveTextEditor()
     if currEditor?
       @disposables.add currEditor.getBuffer().onDidStopChanging =>
         @changeHandler() if atom.config.get 'preview.liveUpdate'
@@ -112,7 +112,7 @@
     updateOnTabChange =
       atom.config.get 'preview.updateOnTabChange'
     if updateOnTabChange
-      currEditor = atom.workspace.getActiveEditor()
+      currEditor = atom.workspace.getActiveTextEditor()
       if currEditor?
         # Stop watching for events on current Editor
         @disposables.dispose()
@@ -211,7 +211,7 @@
     # Update Title
     @emitter.emit 'did-change-title'
     # Start preview processing
-    cEditor = atom.workspace.getActiveEditor()
+    cEditor = atom.workspace.getActiveTextEditor()
     editor = @getEditor()
     # console.log('renderPreviewWithRenderer', rendererName)
     # console.log('editor', editor, cEditor)


Cleanup logging

Cleanup logging

--- lib/python-tools.coffee
+++ lib/python-tools.coffee
@@ -59,8 +59,7 @@
     @provider.kill()

   _deserialize: (response) ->
-    console.log "Got some data back from tools.py"
-    console.log "#{response}"
+    console.log "tools.py => #{response}"

     response = JSON.parse(response)


only select active subjects

only select active subjects

--- app/models/Subject.coffee
+++ app/models/Subject.coffee
@@ -7,25 +7,38 @@
   @configure 'Subject','location', 'metadata', 'active', 'workflow_ids'
   @belongsTo 'archive', Archive

+  constructor:->
+    super
+    @active= true
+
+
   @next_subject:=>
     @purge()
     API.get "/subjects", (data)=>
       Subject.create(data)
       Subject.trigger("gotNext")

+  @active:=>
+    @select (s)=>
+      s.active
+
+  @activeCount:=>
+    @active().length
+
   @current:=>
     Subject.first()

   @getNextForCollection:(collection_id, number=2)=>
     _(number).times =>
-      API.get "/projects/notes_from_nature/groups/#{collection_id}/subjects", (data)=>
+      API.get "/projects/notes_from_nature/groups/#{collection_id}/subjects?limit=#{number}", (data)=>
         Subject.create(data)
+

   @purge:=>
     for subject in Subject.all()
       subject.destroy()

   @random:=>
-    @all()[Math.floor(Math.random()*@count())]
+    @active()[Math.floor(Math.random()*@count())]

 module.exports = Subject


Agent pie code cleanup

Agent pie code cleanup

--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -1,9 +1,6 @@
 class Dashing.FullpieAgent extends Dashing.Widget

     @accessor 'data'
-
-    ready: ->
-        @oldPieData

     onData: (data) ->
         #$(@node).fadeOut().fadeIn()


newpie experiments

newpie experiments

--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -21,7 +21,7 @@
         # Config values here
         width = 580                             # Width of the SVG area
         height = 400                            # Height of the SVG area
-        width = (Dashing.widget_base_dimensions[0] * container.data("sizex")) - Dashing.widget_margins[0] * 2 * (container.data("sizex") - 1)
+        width = (Dashing.widget_base_dimensions[0] * container.data("sizex")) - Dashing.widget_margins[0] * 4 * (container.data("sizex") - 1)
         height = (Dashing.widget_base_dimensions[1] * container.data("sizey")) - 120 # Allow for header and footer text
         console.log('w/h',width + '/' + height)
         radius = Math.min(width, height) / 2    # Calculated min dimension of the SVG area


fullpieagent testing

fullpieagent testing

--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -70,6 +70,11 @@
       @arc b

   update: (dataSet) ->
+
+    if !dataSet
+        dataSet = @get("data")
+    if !dataSet
+        return

     width = 750
     height = 450


vm: hostname alias is required

vm: hostname alias is required

also, get the group id from the embedded document, rather than searching for it inside of the string (those ids should be opaque; should not carry data inside them)

--- workers/social/lib/social/models/vm.coffee
+++ workers/social/lib/social/models/vm.coffee
@@ -51,7 +51,9 @@
       ldapPassword      :
         type            : String
         default         : -> null
-      hostnameAlias     : String
+      hostnameAlias     :
+        type            : String
+        required        : yes
       hostKite          :
         type            : String
         default         : -> null
@@ -65,7 +67,7 @@
                           ]]
         default         : if argv.c is 'vagrant' then 'vagrant' else 'sj'
       webHome           : String
-      planOwner         : String
+      # planOwner         : String
       planCode          : String
       vmType            :
         type            : String
@@ -167,6 +169,8 @@
   @createAliases = ({nickname, type, uid, groupSlug})->
     domain       = 'kd.io'
     aliases      = []
+    type        ?= 'user'
+
     if type in ['user', 'expensed']
       if uid is 0
         aliases.push "#{nickname}.#{groupSlug}.#{domain}"
@@ -202,6 +206,8 @@
         return {groupSlug, prefix, nickname, uid, type:'user', alias}
     return null

+
+  @createVmByNonce
   # TODO: this needs to be rethought in terms of bundles, as per the
   # discussion between Devrim, Chris T. and Badahir  C.T.
   @createVm = ({account, type, groupSlug, usage, planCode}, callback)->
@@ -215,11 +221,7 @@
         # We are keeping this names just for counter
         webHome     = groupSlug

-        {nickame} = account.profile
-
-        planOwner   = switch type
-          when 'user'     then "user_#{account._id}"
-          when 'expensed' then "group_#{group._id}"
+        {nickname} = account.profile

         counterName = "#{groupSlug}~#{nickname}~"
         webHome     = nickname
@@ -243,7 +245,6 @@

           vm = new JVM {
             hostnameAlias
-            planOwner
             planCode
             webHome
             groups
@@ -255,7 +256,6 @@
           vm.save (err) =>

             if err
-              console.error err
               return console.warn "Failed to create VM for ", \
                                    {users, groups, hostnameAlias}

@@ -380,8 +380,8 @@
       @fetchAccountVmsBySelector delegate, selector, options, callback

   @fetchVms = secure (client, options, callback) ->
-      {delegate} = client.connection
-      @fetchAccountVmsBySelector delegate, {}, options, callback
+    {delegate} = client.connection
+    @fetchAccountVmsBySelector delegate, {}, options, callback

     # TODO: let's implement something like this:
     # failure: (client, callback) ->
@@ -489,10 +489,9 @@
         if vm.planOwner.indexOf("user_") > -1
           @deleteVM vm, callback
         else
-          groupID = vm.planOwner.split('_')[1]
-
+          [{ id: groupId }] = vm.groups
           JGroup = require './group'
-          JGroup.one {_id: groupID}, (err, group)=>
+          JGroup.one { _id: groupId }, (err, group)=>
             return callback err  if err
             JPermissionSet.checkPermission client, "delete vms", group,
             (err, hasPermission)=>


Fix: Dropped out of insert mode on location change

For example, it would be nice to usually be in insert mode in gmail, and
only occasionally leave it. However, as soon as you for example went from
the inbox to an e-mail, the location changed, which caused normal mode to
be entered. Only when in hints mode we need to do that (issue #101).

--- extension/packages/events.coffee
+++ extension/packages/events.coffee
@@ -105,12 +105,15 @@

 # This listener works on individual tabs within Chrome Window
 tabsListener =
-  # Listenfor location changes, disable the extension on blacklisted urls, and make sure that we
-  # start out in normal mode
+  # Listenfor location changes and disable the extension on blacklisted urls
   onLocationChange: (browser, webProgress, request, location) ->
     return unless vim = vimBucket.get(browser.contentWindow)

-    vim.enterNormalMode()
+    # If the location changes when in hints mode (for example because the reload button has been
+    # clicked), we're going to end up in hints mode without any markers. So switch back to normal
+    # mode in that case.
+    if vim.mode == 'hints'
+      vim.enterNormalMode()

     blacklisted = utils.isBlacklisted(location.spec, getPref('black_list'))
     vim.blacklisted = blacklisted


Fixed an issue initializing signature pad on pages that didn't include it

--- app/assets/javascripts/agreements.js.coffee
+++ app/assets/javascripts/agreements.js.coffee
@@ -1,5 +1,6 @@

 @agreementsReady = () ->
   sig = $('#signature').val() if $('#signature').val()
-  $("[data-object~='signature']").signaturePad( drawOnly: true, lineWidth: 0 ).regenerate(sig)
+  if $("[data-object~='signature']").length > 0
+    $("[data-object~='signature']").signaturePad( drawOnly: true, lineWidth: 0 ).regenerate(sig)
   false


Bind JS event at proper time

Bind JS event at proper time

--- app/assets/javascripts/bot_instances.coffee
+++ app/assets/javascripts/bot_instances.coffee
@@ -2,6 +2,7 @@
 # All this logic will automatically be available in application.js.
 # You can use CoffeeScript in this file: http://coffeescript.org/

-$('.owner-instance-key').click (e) ->
-  e.preventDefault();
-  prompt("Your instance key:", $(this).data("key"));
+$(document).on 'turbolinks:load', ->
+  $('.owner-instance-key').on 'click', (e) ->
+    e.preventDefault();
+    prompt("Your instance key:", $(this).data("key"));


updated specs

updated specs

--- spec/block-cursor-spec.coffee
+++ spec/block-cursor-spec.coffee
@@ -1,4 +1,19 @@
 blockCursor = require '../lib/block-cursor'
+
+testColor = (index) ->
+  configKey = if index > 0 then 'secondaryColor' else 'primaryColor'
+  style = blockCursor.getColorStyleElement()
+
+  initialColor = atom.config.get "block-cursor.#{configKey}"
+  initialRule = style.sheet.cssRules[index].cssText
+
+  newColor = initialColor
+  newColor.red = (newColor.red + 1) % 256
+  atom.config.set "block-cursor.#{configKey}", newColor
+  newRule = style.sheet.cssRules[index].cssText
+
+  expect(newRule).not.toEqual initialRule
+  expect(newRule).toMatch /rgba?\(([0-9]{1,3}(,?\s?)?){3,4}\)/

 describe 'Block Cursor', ->
   beforeEach ->
@@ -28,17 +43,15 @@
       it 'changes the second rule of the packages stylesheet', ->
         testColor 1

-testColor = (index) ->
-  configKey = if index > 0 then 'secondaryColor' else 'primaryColor'
-  style = blockCursor.getColorStyleElement()
+    describe 'the enablePulse option', ->
+      it 'adds the block-cursor-pulse class to the workspaceView if enabled', ->
+        workspaceView = atom.views.getView atom.workspace
+        console.log workspaceView.className
+        atom.config.set 'block-cursor.pulseEnabled', true
+        console.log workspaceView.className
+        expect(workspaceView.className).toMatch /\s*block-cursor-pulse\s*/

-  initialColor = atom.config.get "block-cursor.#{configKey}"
-  initialRule = style.sheet.cssRules[index].cssText
-
-  newColor = initialColor
-  newColor.red = (newColor.red + 1) % 256
-  atom.config.set "block-cursor.#{configKey}", newColor
-  newRule = style.sheet.cssRules[index].cssText
-
-  expect(newRule).not.toEqual initialRule
-  expect(newRule).toMatch /rgba?\(([0-9]{1,3}(,?\s?)?){3,4}\)/+      it 'removes the block-cursor-pulse class from the workspaceView if disabled', ->
+        workspaceView = atom.views.getView atom.workspace
+        atom.config.set 'block-cursor.pulseEnabled', false
+        expect(workspaceView.className).not.toMatch /block-cursor-pulse/


revise updated chain usage

revise updated chain usage

--- running.coffee
+++ running.coffee
@@ -7,11 +7,10 @@
   getValue: -> @_value.get()

   # create the chain
-  _chain: new Chain('chain') # 'chain' type, not pipeline.
+  _chain: new Chain

   # TODO: validate it's a function
-  onChange: (action) ->
-    @_chain.add action
+  onChange: (action) -> @_chain.add action

   start: (options) ->