Taking into account null rects and library width/height subtraction on sprite parsing.

--- app/lib/sprites/SpriteParser.coffee
+++ app/lib/sprites/SpriteParser.coffee
@@ -24,6 +24,11 @@
       @animationLongKeys[longKey] = shortKey

   parse: (source) ->
+    # Grab the library properties' width/height so we can subtract half of each from frame bounds
+    properties = source.match(/.*lib\.properties = \{\n.*?width: (\d+),\n.*?height: (\d+)/im)
+    @width = parseInt(properties[1] ? "0", 10)
+    @height = parseInt(properties[2] ? "0", 10)
+
     options = {loc: false, range: true}
     ast = esprima.parse source, options
     blocks = @findBlocks ast, source
@@ -178,10 +183,17 @@
                 else if arg.type is 'AssignmentExpression'
                   bounds = @grabFunctionArguments argSource.replace('rect=', ''), true
                   lastRect = bounds
+                else if arg.type is 'Literal' and arg.value is null
+                  bounds = [0, 0, 1, 1]  # Let's try this.
                 frameBounds.push bounds
           else
             console.log "Didn't have multiframe bounds for this movie clip!"
             frameBounds = [nominalBounds]
+
+          # Subtract half of width/height parsed from lib.properties
+          for bounds in frameBounds
+            bounds[0] -= @width / 2
+            bounds[1] -= @height / 2

           functionExpressions.push {name: name, bounds: nominalBounds, frameBounds: frameBounds, expression: node.parent.parent, kind: kind}
     @walk ast, null, gatherFunctionExpressions


Go off visable logs, check against models

--- SingularityUI/app/components/aggregateTail/AggregateTail.cjsx
+++ SingularityUI/app/components/aggregateTail/AggregateTail.cjsx
@@ -77,8 +77,9 @@
     )[0]?.taskId.instanceNo

   renderIndividualTails: ->
-    if @state.activeTasks.length > 0
-      @state.viewingInstances.map((taskId, i) =>
+    @state.viewingInstances.map((taskId, i) =>
+      if @props.logLines[taskId]
+        console.log @props.logLines[taskId]
         <div key={taskId} id="tail-#{taskId}" className="col-md-#{@getColumnWidth()} tail-column">
           <IndividualTail
             ref="tail_#{i}"
@@ -92,7 +93,7 @@
             activeTasks={@props.activeTasks}
             closeTail={@toggleViewingInstance} />
         </div>
-      )
+    )

   render: ->
     <div>


follow docs

follow docs

--- src/follow.coffee
+++ src/follow.coffee
@@ -1,6 +1,8 @@
 ###*
 The Follow module provides a simple method to set an object's
 velocity so that it will approach another object.
+
+

 This method relies on both objects having `position` methods.
 All GameObjects have this method by default.


app: Mask user sensitive info

app: Mask user sensitive info

--- src/js/app.coffee
+++ src/js/app.coffee
@@ -19,10 +19,14 @@

 logInfo = ->
-  console.log "accessToken: #{Settings.option 'accessToken'}"
+  accessToken = Settings.option 'accessToken'
+  if accessToken?
+    accessToken = "#{accessToken[0..5]}..."
+
+  console.log "accessToken: #{accessToken}"
   console.log "Version: #{Appinfo.versionLabel}"
   Pebble.getTimelineToken?(
-    (token) -> console.log "Timeline user token: #{token}"
+    (token) -> console.log "Timeline user token: #{token[0..5]}..."
     (errorString) -> console.log errorString
   )


Disallow pause while plummetting - fix #12

--- app/scripts/controllers/main.coffee
+++ app/scripts/controllers/main.coffee
@@ -93,6 +93,7 @@
       color: get_color()

     $scope.$on 'pause', (event) ->
+      return if $scope.game_info.plumetting_block
       $scope.game_info.in_progress = false
       cancel_game_interval()


Fix HTTP client tests, improve HTTP client helper.

--- test/solid.coffee
+++ test/solid.coffee
@@ -8,17 +8,25 @@

 PORT = solid.DEFAULT_PORT
 HOST = "127.0.0.1"
-
+
 # HTTP client testing helpers
-# TODO: This doesn't handle errors all that well

-get = (path, cb, port=PORT) ->
+get = (path, options, callback) ->
+  if not callback
+    callback = options
+    options  = null
+  options ?= { port : PORT }
+
   data = ""
-  console.log "About to do a GET #{HOST}:#{port}#{path}"
-  http.get {host: HOST, port:port, path:path}, (res) ->
+  port = options.port
+  console.log "About to do a GET on #{HOST}:#{port}#{path}"
+  request = http.get host: HOST, port: port, path: path, (res) ->
     res.on 'data', (chunk) -> data += chunk
-    res.on 'end', -> cb res, data
-
+    res.on 'end', -> callback res, data
+  request.on 'error', (error) ->
+    console.error "Error in GET request:"
+    console.error error.stack
+
 # Tests
 # =====

@@ -26,49 +34,48 @@
   # describe "basic", ->
   #   it "should return some HTML with a 200 status code", (done) ->
   #     homeHTML = "<b>hello world!</b>"
-  #
+  #
   #     server = solid ->
   #               "/"          : -> homeHTML
   #               "jquery"     : @jquery
-  #
+  #
   #     console.log "Started server"
-  #
+  #
   #     get "/", (res, data) ->
   #       console.log "Got some data #{data}"
   #       res.statusCode.should.equal 200
   #       data.should.equal homeHTML
-  #
+  #
   #       get "/jquery", (res, data) ->
   #         res.headers['content-type'].should.equal 'text/javascript'
   #         res.statusCode.should.equal 200
   #         data.should.equal fs.readFileSync './external-libs/jquery.min.js', 'utf8'
   #         server.close()
   #         done()
-
+
   describe "redirects", ->
-    it "should redirect and return a 302", (done) ->
+    it "should redirect and return a 302", (done) ->
       server = solid ->
                 "/"          : -> "home"
                 "/home"      : "/"
-
+
       get "/home", (res, data) ->
         res.statusCode.should.equal 302
         res.headers.location.should.equal "http://#{HOST}:#{PORT}/"
         server.close()
         done()
-
+
   describe "static files", ->
     it "should serve all files in the static option as static files", (done) ->
       done() # TODO
-
-  describe "server configuration", ->
+
+  describe "configuration", ->
     it "should start a server on a port other than the specified port rather than the default one", (done) ->
       port = 65432
       server = solid {port: port}, ->
                   "/" : -> "home"
-      get "/", ((res, data) ->
+      get "/", port: port, (res, data) ->
           res.statusCode.should.equal 200
           data.should.equal "home"
           server.close()
-          done()),
-        port+          done()


Dump schema for inspection at Travis CI.

Going to run my failing tests at Travis CI with a `console.log` to see
the results of building a schema hash in the compiler harness.

--- t/compiler/harness.coffee
+++ t/compiler/harness.coffee
@@ -27,5 +27,6 @@
         for column in results
           (schema[column.TABLE_NAME] or= []).push(column.COLUMN_NAME)
         client.destroy()
+        console.log schema
         callback null, schema
 module.exports = require("proof") context


don't leak dialog boxes

don't leak dialog boxes

--- src/textorum/tinymce/element.coffee
+++ src/textorum/tinymce/element.coffee
@@ -126,7 +126,8 @@
         else
           attrrequired[attr] = elementattrs[attr]
       if attrform.childNodes.length
-        w = @editor.windowManager.open {
+        wm = @editor.windowManager
+        w = wm.open {
           inline: true
           resizable: true
           title: "Edit #{newtagname}"
@@ -135,12 +136,12 @@
             text: 'Ok'
             click: (e) ->
               console.log "OK button clicked:", e, w
-              w.element.dialog("close")
+              wm.close(null, w.id)
           }, {
             text: 'Cancel'
             click: (e) ->
               console.log "Cancel button clicked:", e, w
-              w.element.dialog("close")
+              wm.close(null, w.id)
           }]
         }
       else


still force completion for now

still force completion for now

--- lib/atom-ternjs-autocomplete.coffee
+++ lib/atom-ternjs-autocomplete.coffee
@@ -134,7 +134,9 @@

     forceCompletion: ->
         @force = true
-        @autocompletePlus.autocompleteManager.runAutocompletion()
+        # need this for now. no plan, to hook this forever
+        @autocompletePlus.autocompleteManager.shouldDisplaySuggestions = true
+        @autocompletePlus.autocompleteManager.findSuggestions()
         @force = false

     clearSuggestionsAndHide: ->


Fix dealer diag of playcache

Fix dealer diag of playcache

--- js/token-poker/dealer.coffee
+++ js/token-poker/dealer.coffee
@@ -16,7 +16,7 @@
   diagnostic: ->
     ["@gameClasses: #{(gameClass.name for gameClass in @gameClasses).join(',')}",
      "@id: #{@id}",
-     "@playCache: #{([play.playerName, play.playerHand] for play in @playCache).join(',')}",
+     "@playCache: #{([play.player, play.playerHand] for play in @playCache).join(',')}",
      (if (@game && @game.diagnostic) then @game.diagnostic() else '')].join("\n")

   listGames: ->


pre fill sickness, vacation and army days to 1, workday to 9 and weekends and holidays to 0

--- app/assets/javascripts/hours_report/timesheets/edit.js.coffee
+++ app/assets/javascripts/hours_report/timesheets/edit.js.coffee
@@ -11,7 +11,12 @@
       valueInput.removeClass(classes).addClass(@.value)
       commentInput.removeClass(classes).addClass(@.value)

-      valueInput.val(0) unless @.value == "workday"
+      if @.value == "workday"
+        valueInput.val(9)
+      if @.value == "sickness" or @.value == "vacation" or @.value == "army"
+        valueInput.val(1)
+      if @.value == "weekend" or @.value == "holiday"
+        valueInput.val(0)

     $("#prefill-working-days").click ->
       for day in $(".edit_timesheet fieldset")


Add serializeHasMany to PaperSerialzer

Add serializeHasMany to PaperSerialzer

Per Chris, we may want to inherit from an ApplicationSerializer base
class.  Couldn't get it loading properly and decided on just duplicating
for now.

--- app/assets/javascripts/serializers/paper_serializer.js.coffee
+++ app/assets/javascripts/serializers/paper_serializer.js.coffee
@@ -13,6 +13,12 @@
       delete phase.task_ids

     payload
+
+  serializeHasMany: (record, json, relationship) ->
+    key = relationship.key
+    relationshipType = DS.RelationshipChange.determineRelationshipType(record.constructor, relationship)
+    json[key] = Em.get(record, key).mapBy("id")  if relationshipType is "manyToNone" or relationshipType is "manyToMany"
+    return

   extractSingle: (store, primaryType, payload, recordId, requestType) ->
     payload = @normalizePayload(primaryType, payload)


electricity consumption support for fans

--- lib/controls/FanSpeedControl.coffee
+++ lib/controls/FanSpeedControl.coffee
@@ -26,6 +26,14 @@
     speedName: _.findWhere(speed.getAttribute('choices'),
       id: speed.getDatum('state'))?.longName

+  _getConsumptionRates: ->
+    state = @_getState()
+    rating = @get('parameters').ratedElectricalPower
+    if rating?[state.speed]?
+      electricity: rating[state.speed]
+    else
+      null
+
   resolveSpeedName: (name) ->
     name = name.toLowerCase()
     speed = @getConnectionTarget('discreteSpeed').getAspect('discreteSpeed')


Added logger.stop()

Added logger.stop()

--- scripts/helpers/logger.coffee
+++ scripts/helpers/logger.coffee
@@ -4,20 +4,19 @@
   'backbone'
 ], ($, _, Backbone) ->

-  wrapper = (trigger) ->
-    return _.wrap trigger, (originalTrigger) ->
-      args = _.toArray(arguments).slice(1)
+  isEnabled = true

+  Backbone.Events.trigger = _.wrap Backbone.Events.trigger, (originalTrigger) ->
+    args = _.toArray(arguments).slice(1)
+
+    if isEnabled
       log = "trigger: #{ args }"
       $.post('/logging', log)

-      originalTrigger.apply(@, args)
+    originalTrigger.apply(@, args)

-  logger = {
-    start: () ->
-      Backbone.Events.trigger = wrapper(Backbone.Events.trigger)
-  };
+  class Logger
+    start: () -> isEnabled = true
+    stop: () -> isEnabled = false

-  logger.start()
-
-  return logger
+  return new Logger()


show deal image

show deal image

--- scripts/steam.coffee
+++ scripts/steam.coffee
@@ -34,7 +34,7 @@
   robot.respond /daily deal|сделка дня/i, (msg) ->
     getItems msg, location, parseDeals, 1, (deal) ->
       if deal.url
-        msg.send "#{deal.discount} // #{deal.url}"
+        msg.send "#{deal.discount} // #{deal.url} (deal.image)"
       else
         msg.send "Сегодня сделки дня нет"


bongo: fix type error regression

bongo: fix type error regression

--- node_modules_koding/bongo/lib/model/index.coffee
+++ node_modules_koding/bongo/lib/model/index.coffee
@@ -876,8 +876,9 @@
           splittedPath = path.split '.'
           type = Model.getType model, splittedPath
           validators = model.constructor.validators[path]
-          for { validate, name, message } in validators when not validate value
-            return callback { message, name, value }
+          if validators?.length
+            for { validate, name, message } in validators when not validate value
+              return callback { message, name, value }
           if type and type isnt Object
             value = Model.castValue value, type
             operation[path] = value


Split platform specific keybindings

Split platform specific keybindings

--- keymaps/command-palette.cson
+++ keymaps/command-palette.cson
@@ -1,2 +1,5 @@
-'body, .command-palette .editor':
-  'meta-P': 'command-palette:toggle'
+'.platform-darwin, .platform-darwin .command-palette .editor':
+  'cmd-P': 'command-palette:toggle'
+
+'.platform-win32, .platform-win32 .command-palette .editor':
+  'ctrl-P': 'command-palette:toggle'


Fix incorrect date in logger.

Fix incorrect date in logger.

--- src/logger.coffee
+++ src/logger.coffee
@@ -9,7 +9,7 @@
   debug: 'blue'

 getInfo = (level) ->
-  date = new Date().toFormat('DD MMM HH:MM:SS')
+  date = new Date().toFormat('DD MMM HH24:MI:SS')
   lvl = color.set level, colors[level]
   "#{date} - #{lvl}:"


Tweak attribute highlighting

Tweak attribute highlighting

--- grammars/lean.cson
+++ grammars/lean.cson
@@ -48,7 +48,8 @@
   }
   {
     match: "@\\[[^\\]]*\\]"
-    name: "storage.attribute.lean"
+    name: "comment.attribute.lean"
+    todo: "not sure how to do this?"
   }
   {
     match: "\\b(import|prelude|tactic_hint|theory|protected|private|noncomputable|opaque|definition|renaming|hiding|exposing|parameter|parameters|begin|proof|qed|conjecture|constant|constants|hypothesis|lemma|corollary|variable|variables|premise|premises|print|theorem|example|abbreviation|context|open|as|export|axiom|inductive|with|structure|record|universe|universes|alias|help|environment|options|override|precedence|reserve|postfix|prefix|match|infix|infixl|infixr|notation|eval|check|exit|coercion|end|using|namespace|section|fields|find_decl|attribute|local|set_option|add_rewrite|extends|include|omit|classes|instances|coercions|metaclasses|raw)\\b"


:art: Move scopeName and name to top

:art: Move scopeName and name to top

--- grammars/perl.cson
+++ grammars/perl.cson
@@ -1,14 +1,14 @@
-'comment': '\n\tTODO:\tInclude RegExp syntax\n'
+'scopeName': 'source.perl'
+'name': 'Perl'
 'fileTypes': [
   'pl'
+  'PL'
   'pm'
   'pod'
+  'psgi'
   't'
-  'PL'
-  'psgi'
 ]
 'firstLineMatch': '^#!.*\\bperl\\b'
-'name': 'Perl'
 'patterns': [
   {
     'include': '#line_comment'
@@ -1747,4 +1747,3 @@
         'name': 'variable.other.readwrite.global.special.perl'
       }
     ]
-'scopeName': 'source.perl'


Fix indentation

Fix indentation

--- resources/assets/coffee/react/_components/beatmapset-panel.coffee
+++ resources/assets/coffee/react/_components/beatmapset-panel.coffee
@@ -153,12 +153,12 @@
                   pattern: osu.trans 'beatmapsets.show.details.mapped_by'
                   mappings:
                     ':mapper':
-                        a
-                          key: 'mapper'
-                          href: laroute.route('users.show', user: beatmapset.user_id)
-                          className: 'js-usercard'
-                          'data-user-id': beatmapset.user_id
-                          strong null, beatmapset.creator
+                      a
+                        key: 'mapper'
+                        href: laroute.route('users.show', user: beatmapset.user_id)
+                        className: 'js-usercard'
+                        'data-user-id': beatmapset.user_id
+                        strong null, beatmapset.creator
               div
                 className: 'u-ellipsis-overflow'
                 if beatmapset.status in ['graveyard', 'wip', 'pending']


looks ready

looks ready

--- src/js/app.coffee
+++ src/js/app.coffee
@@ -42,7 +42,7 @@
   accessToken = Settings.option 'accessToken'
   unless accessToken?
     displaySignInWindow()
-    return
+    failure("accessToken is needed")

   ajax
     url: opt.url
@@ -58,7 +58,7 @@
       if status == 401
         console.log "Server says that authorization is required"
         displaySignInWindow()
-      console.log "Request failure (#{status} #{method} #{url})"
+      console.log "Request failure (#{status} #{opt.method} #{opt.url})"
       failure(response, status, req)

 reloadShow = (showID, success, failure) ->
@@ -81,19 +81,15 @@
         (response, status, req) ->
           # console.log "getToWatchList: asked "
           # console.log "returned: #{JSON.stringify response.seasons}"
-          if status != 200
-            doneItem(response: response, status: status, req: req)
           item.next_episode = response.next_episode
           item.seasons = response.seasons
           # for season in item.seasons
           #   for episode in season.episodes
           #     episode.completed ?= false
           doneItem()
+        (response, status, req) ->
+          doneItem()
     (err) ->
-      if err?
-        console.log "Failed response (#{err.status}): #{err.response}"
-        console.log "Request was: #{err.req}"
-      # console.log "getToWatchList returning"
       callback(showListUpdated) if callback?
   )

@@ -108,6 +104,8 @@
   signInWindow.show()

 isNextEpisodeForItemAired = (item) ->
+  # console.log "isNextEpisodeForItemAired of item: #{JSON.stringify item.show.title}"
+  # console.log "item.next_episode #{JSON.stringify item.next_episode}"
   return false unless item.next_episode?
   if item.next_episode.season > item.seasons.length
     return false
@@ -212,43 +210,65 @@
       isNextEpisodeListed: opt.isNextEpisodeListed
   }

-getEpisodeData = (showID, seasonNumber, episodeNumber, callback) ->
+getOrFetchEpisodeData = (showID, seasonNumber, episodeNumber, callback) ->
   # toWatchMenu.on 'select', (e) ->
   # element = e.item
-  item = i for i in shows if i.show.ids.trakt == showID
+  console.log "getOrFetchEpisodeData for #{showID}, #{seasonNumber}, #{episodeNumber}"
+  item = i for i in shows when i.show.ids.trakt == showID
+  # console.log "item: #{JSON.stringify item}"
   season = s for s in item.seasons when s.number == seasonNumber
+  unless season?
+    season =
+      number: seasonNumber
+      aired: 0
+      completed: 0
+      episodes: []
+  # console.log "season: #{JSON.stringify season}"
   episode = e for e in season.episodes when e.number == episodeNumber
+  # console.log "episode: #{JSON.stringify episode}"
+  unless episode?
+    episode =
+      number: episodeNumber
+      completed: false
+    season.episodes.push episode
+  console.log "Considering episode: #{JSON.stringify episode}"

   episode.seasonNumber = seasonNumber
   episode.episodeNumber = episodeNumber
   episode.showID = showID
-
-  unless episode.id? and episode.title? and episode.overview?
+  episode.showTitle = item.show.title
+
+  getOrFetchOverview = (success) ->
+    if episode.overview?
+      console.log "Overview already available"
+      success(episode)
+      return
+    console.log "fetching overview..."
+    traktvRequest "/search?id_type=trakt-episode&id=#{episode.episodeID}",
+      (response, status, req) ->
+        console.log "Fetched overview: #{response}"
+        if response
+          episode.overview = response[0].episode.overview
+        success(episode) if success?
+
+  fetchEpisodeIDAndTitle = (showID, seasonNumber, episodeNumber, successFetchEpisodeIDAndTitle) ->
     traktvRequest "/shows/#{showID}/seasons/#{seasonNumber}/episodes/#{episodeNumber}",
-    (response, status, req) ->
-      episode.id = response.ids.trakt
-      episode.title = response.title
-
-      if episode.overview?
-        callback(episode) if callback?
-      else
-        traktvRequest "/search?id_type=trakt-episode&id=#{episode.id}",
-          (response, status, req) ->
-            if response
-              episode.overview = response[0].episode.overview
-            callback(episode) if callback?
-
-
-  traktvRequest "/shows/#{element.data.showID}/seasons/#{element.data.seasonNumber}/episodes/#{element.data.episodeNumber}",
-    (response, status, req) ->
-      showTitle = item.show.title for item in shows when item.show.ids.trakt == element.data.showID
-      detailedItemCard = new UI.Card(
-        title: showTitle
-        subtitle: "Season #{element.data.seasonNumber} Ep. #{element.data.episodeNumber}"
-        body: "Title: #{response.title}"
-        style: 'small'
-      )
-      detailedItemCard.show()
+      (response, status, req) ->
+        console.log "fetchEpisodeIDAndTitle response: #{response}"
+        episode.episodeID = response.ids.trakt
+        episode.episodeTitle = response.title
+
+        successFetchEpisodeIDAndTitle(episode) if successFetchEpisodeIDAndTitle?
+
+  if episode.episodeID? and episode.episodeTitle?
+    console.log "going to fetch overview"
+    getOrFetchOverview callback
+  else
+    console.log "going to fetch ep id and title"
+    fetchEpisodeIDAndTitle showID, seasonNumber, episodeNumber,
+      (episode) ->
+        console.log "fetched id and title: #{JSON.stringify episode}"
+        getOrFetchOverview callback

@@ -280,10 +300,6 @@
             completed: false
     toWatch

-  # console.log "item: #{key}: #{value}" for key, value of item for item in shows
-  # console.log "data: #{JSON.stringify data}"
-
-
   toWatchMenu = new UI.Menu
     sections:
       {
@@ -300,7 +316,18 @@
       } for item in shows when isNextEpisodeForItemAired(item)

   toWatchMenu.on 'longSelect', (e) ->
+    element = e.item
     data = e.item.data
+    data.previousSubtitle = element.subtitle
+
+    element.subtitle =
+      if element.data.completed
+        "Unchecking..."
+      else
+        "Checking..."
+
+    toWatchMenu.item(e.sectionIndex, e.itemIndex, element)
+
     modifyCheckState
       showID: data.showID
       episodeNumber: data.episodeNumber
@@ -317,6 +344,9 @@
           element.data.completed = false
           element.icon = ICON_MENU_UNCHECKED

+        element.subtitle = element.data.previousSubtitle
+        delete element.data.previousSubtitle
+
         toWatchMenu.item(e.sectionIndex, e.itemIndex, element)

         if isNowCompleted and not element.isNextEpisodeListed
@@ -339,17 +369,17 @@

   toWatchMenu.on 'select', (e) ->
     element = e.item
-    traktvRequest "/shows/#{element.data.showID}/seasons/#{element.data.seasonNumber}/episodes/#{element.data.episodeNumber}",
-      (response, status, req) ->
-        showTitle = item.show.title for item in shows when item.show.ids.trakt == element.data.showID
-        detailedItemCard = new UI.Card(
-          title: showTitle
-          subtitle: "Season #{element.data.seasonNumber} Ep. #{element.data.episodeNumber}"
-          body: "Title: #{response.title}"
-          style: 'small'
-        )
-        detailedItemCard.show()
-
+    data = element.data
+    getOrFetchEpisodeData data.showID, data.seasonNumber, data.episodeNumber, (episode) ->
+      detailedItemCard = new UI.Card(
+        title: episode.showTitle
+        subtitle: "Season #{episode.seasonNumber} Ep. #{episode.episodeNumber}"
+        body: "Title: #{episode.episodeTitle}\n\
+               Overview: #{episode.overview}"
+        style: 'small'
+        scrollable: true
+      )
+      detailedItemCard.show()
   toWatchMenu.show()

   callback() if callback?
@@ -371,9 +401,9 @@
                 seasonNumber: item.episode.season
                 episodeNumber: item.episode.number

-            } for item in items
+            } for item in items when moment(item.airs_at) >= moment()
         } for date, items of response
-      console.log "sections: #{JSON.stringify sections}"
+      # console.log "sections: #{JSON.stringify sections}"

       upcomingMenu = new UI.Menu(
         sections: sections
@@ -383,16 +413,29 @@

       upcomingMenu.on 'select', (e) ->
         element = e.item
-        traktvRequest "/shows/#{element.data.showID}/seasons/#{element.data.seasonNumber}/episodes/#{element.data.episodeNumber}",
-          (response, status, req) ->
-            showTitle = item.show.title for item in shows when item.show.ids.trakt == element.data.showID
-            detailedItemCard = new UI.Card(
-              title: showTitle
-              subtitle: "Season #{element.data.seasonNumber} Ep. #{element.data.episodeNumber}"
-              body: "Title: #{response.title}"
-              style: 'small'
-            )
-            detailedItemCard.show()
+        data = element.data
+        getOrFetchEpisodeData data.showID, data.seasonNumber, data.episodeNumber, (episode) ->
+          console.log "response for #{data.showID}, #{data.seasonNumber}, #{data.episodeNumber}"
+          console.log "--> #{JSON.stringify episode}"
+          detailedItemCard = new UI.Card(
+            title: episode.showTitle
+            subtitle: "Season #{episode.seasonNumber} Ep. #{episode.episodeNumber}"
+            body: "Title: #{episode.episodeTitle}\n\
+                   Overview: #{episode.overview}"
+            style: 'small'
+            scrollable: true
+          )
+          detailedItemCard.show()
+        # traktvRequest "/shows/#{element.data.showID}/seasons/#{element.data.seasonNumber}/episodes/#{element.data.episodeNumber}",
+        #   (response, status, req) ->
+        #     showTitle = item.show.title for item in shows when item.show.ids.trakt == element.data.showID
+        #     detailedItemCard = new UI.Card(
+        #       title: showTitle
+        #       subtitle: "Season #{element.data.seasonNumber} Ep. #{element.data.episodeNumber}"
+        #       body: "Title: #{response.title}"
+        #       style: 'small'
+        #     )
+        #     detailedItemCard.show()
       callback() if callback?

 displayShowsMenu = (callback) ->
@@ -438,20 +481,50 @@
     seasonsMenu.on 'select', (e) ->
       data = e.item.data
       season = s for s in item.seasons when s.number == data.seasonNumber
-      episodesMenu = new UI.Menu
-        sections: [{
-          items:
-            createToWatchMenuItem(
-              showID: data.showID
-              episodeTitle: "bo"
-              seasonNumber: data.seasonNumber
-              episodeNumber: episode.number
-              completed: false
-            ) for episode in season.episodes
-        }]
-      episodesMenu.show()
-
-  callback() if callback?
+      async.map(
+        season.episodes,
+        (ep, callbackResult) ->
+          getOrFetchEpisodeData data.showID, data.seasonNumber, ep.number,
+            (episode) -> callbackResult(null, episode)
+        (err, episodes) ->
+          console.log "------results of map: err #{err} --------------"
+          if err?
+            console.log "------results of map: err #{err} --------------"
+            return;
+          console.log "------results of map: 1st: #{JSON.stringify episodes[0]}"
+          episodesMenu = new UI.Menu
+            sections: [{
+              items:
+                {
+                  title: episode.episodeTitle
+                  subtitle: "Season #{episode.seasonNumber} Ep. #{episode.episodeNumber}"
+                  data:
+                    episodeTitle: episode.episodeTitle
+                    overview: episode.overview
+                    seasonNumber: episode.seasonNumber
+                    episodeNumber: episode.episodeNumber
+                    showID: episode.showID
+                    showTitle: episode.showTitle
+                } for episode in episodes
+            }]
+          episodesMenu.show()
+          episodesMenu.on 'select', (e) ->
+            data = e.item.data
+            detailedItemCard = new UI.Card(
+              title: data.showTitle
+              subtitle: "Season #{data.seasonNumber} Ep. #{data.episodeNumber}"
+              body: "Title: #{data.episodeTitle}\n\
+                     Overview: #{data.overview}"
+              style: 'small'
+              scrollable: true
+            )
+            detailedItemCard.show()
+
+          callback() if callback?
+        )
+
+
+

 initSettings = ->
   Settings.init()
@@ -470,10 +543,11 @@
       title: 'To watch'
       id: 'toWatch'
     }, {
-      title: 'Upcoming episodes'
+      title: 'Upcoming'
       id: 'upcoming'
     }, {
       title: 'My shows'
+      icon: 'images/icon_home.png'
       id: 'myShows'
     }, {
       title: 'Advanced'


Correctly generate display name for persons who are not users.

--- server/person.coffee
+++ server/person.coffee
@@ -12,10 +12,10 @@
           [fields._id, fields._id]

       fields.displayName.generator = (fields) ->
-        person = new Person fields
-        # Display name is public, so we don't want to leak email until the user registers.
+        person = new Person(fields).refresh(invited: 1)
+        # Display name is public, so we don't want to leak email until the invited user registers.
         # We use a special publish endpoint to provide email addresses of all invitees to inviters.
-        if person.user.refresh(services: 1).isRegistered()
+        if not person.invited?.length or not person.user?.emails or person.user.refresh(services: 1).isRegistered()
           [fields._id, person.getDisplayName true]
         else
           [fields._id, fields._id]


Fixed glyphicon serving.

Fixed glyphicon serving.

--- server/server.coffee
+++ server/server.coffee
@@ -65,6 +65,7 @@
     app.use express.logger('dev')

   app.use(express.static(path.join(__dirname, '../public')))
+  app.use(express.static(path.join(__dirname, '../bower_components/bootstrap')))
   app.use(useragent.express())

   app.use express.favicon()


Add simplified Controller#compose signature

This is, I believe, easier to read.

--- src/chaplin/controllers/controller.coffee
+++ src/chaplin/controllers/controller.coffee
@@ -36,7 +36,7 @@

   # Convenience method to publish the `!composer:compose` event. See the
   # composer for information on parameters, etc.
-  compose: (name, second, third) ->
+  compose: (name) ->
     if arguments.length is 1
       # Retrieve an active composition using the retrieve event.
       item = null
@@ -45,7 +45,7 @@
       item
     else
       # Compose the arguments using the compose method.
-      @publishEvent '!composer:compose', name, second, third
+      @publishEvent '!composer:compose', arguments...

   # Redirection
   # -----------


minors in the bower

minors in the bower

--- library/membrane/bower.coffee
+++ library/membrane/bower.coffee
@@ -59,7 +59,7 @@
     # assets, by using the `pub` env dir. The package installation
     # is per service and automatically will be included in `prelude`.
     @bower: (target, entry, options={}) ->
-        previous = @bowerings or []
+        assert previous = @bowerings or []
         noTarget = "target must be a string"
         noOptions = "options must be an object"
         assert _.isObject(options), noOptions
@@ -127,7 +127,7 @@
                 where = @constructor.identify().underline
                 assert variable = [name, version, where]
                 logger.debug message.cyan, variable...
-            return next undefined
+            return next.call this, undefined

     # This complicated definition is used to produce and then install
     # a method that is going to be cached and used for each request


make the error last in the callback

make the error last in the callback

this follows standard convention a bit better

--- src/math3d.coffee
+++ src/math3d.coffee
@@ -195,12 +195,12 @@
             width           : undefined
             height          : undefined
             renderer        : undefined # 'webgl' or 'canvas' or undefined to choose best
-            background      : [1,1,1]
+            background      : [1,1, 1]
             spin            : false     # if true, image spins by itself when mouse is over it.
             aspect_ratio    : [1, 1, 1] # a triple [x,y,z] of length three, which scales the x,y,z coordinates of everything by the given values.
             stop_when_gone  : undefined # if given, animation, etc., stops when this html element (not jquery!) is no longer in the DOM
             frame           : undefined # frame options
-            callback        : undefined # opts.callback(error, this object)
+            callback        : undefined # opts.callback(this object, error)

         @frameOpts = defaults @opts.frame,
             color           : undefined # defaults to the color-wise negation of the background
@@ -209,10 +209,10 @@

         math3d.loadThreejs (error) =>
             if error
-                return @opts.callback? error
+                return @opts.callback? undefined, error

             if @_init
-                return @opts.callback? undefined, @
+                return @opts.callback? @
             @_init = true

             # IMPORTANT: There is a major bug in three.js -- if you make the width below more than .5 of the window
@@ -247,7 +247,7 @@
             # recieve a change event
             @renderHooks = []

-            @opts.callback? undefined, @
+            @opts.callback? @

     # client code should call this when done adding objects to the scene
     finalize: ->
@@ -526,7 +526,7 @@

         if opts.in_frame
             @_initPointHelper()
-            @_pointHelperVec.set(opts.loc...)
+            @_pointHelperVec.set opts.loc...
             @updateBoundingBox @_pointHelper

         opts.radius = opts.size/1200
@@ -882,7 +882,7 @@
         scene    : required    # {opts:?, obj:?} or url from which to download (via ajax) a JSON string that parses to {opts:?,obj:?}
         element  : required    # DOM element to attach to
         timeout  : 30000       # milleseconds for timing out fetchs
-        callback : undefined   # callback(error, scene object)
+        callback : undefined   # callback(scene object, error)
     # Render a 3-d scene

     create_scene = (scene) ->
@@ -890,12 +890,12 @@

         scene.opts.parent = opts.element

-        scene.opts.callback = (error, sceneobj) ->
+        scene.opts.callback = (sceneobj, error) ->
             if not error
                 if scene.obj?
                     sceneobj.addObj scene.obj
                 sceneobj.finalize()
-            opts.callback? error, sceneobj
+            opts.callback? sceneobj, error

         new Math3dThreeJS scene.opts


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/matrix.coffee
+++ src/matrix.coffee
@@ -271,8 +271,8 @@
   ###*
   Returns a matrix that corresponds to a skew of skewX, skewY.
   @see Matrix#skew
-  @param {Number} sk The amount to translate in the x direction.
-  @param {Number} ty The amount to translate in the y direction.
+  @param {Number} skewX The amount to translate in the x direction.
+  @param {Number} skewY The amount to translate in the y direction.
   @return {Matrix} A matrix transformation representing a translation by tx and ty.
   ###
   Matrix.skew = (skewX, skewY) ->


highlight ntriples as n3, fix #14

highlight ntriples as n3, fix #14

--- src/routes/syntax-highlight.coffee
+++ src/routes/syntax-highlight.coffee
@@ -40,6 +40,7 @@
 		'application/trig':             'n3'
 		'text/turtle':                  'n3'
 		'application/nquads':           'n3'
+		'application/ntriples':         'n3'
 		'application/rdf+xml':          'xml'
 		'text/xml':                     'xml'
 		'text/html':                    'html'


Fix read state saving

Fix read state saving

--- resources/assets/coffee/react/beatmap-discussions/main.coffee
+++ resources/assets/coffee/react/beatmap-discussions/main.coffee
@@ -34,7 +34,9 @@
     # FIXME: update url handler to recognize this instead
     @focusNewDiscussion = document.location.hash == '#new'

-    if !@restoredState
+    if @restoredState
+      @state.readPostIds = new Set(@state.readPostIdsArray)
+    else
       beatmapset = props.initial.beatmapset
       reviewsConfig = props.initial.reviews_config
       showDeleted = true
@@ -407,6 +409,8 @@

   saveStateToContainer: =>
+    # This is only so it can be stored with JSON.stringify.
+    @state.readPostIdsArray = [@state.readPostIds...]
     @props.container.dataset.beatmapsetDiscussionState = JSON.stringify(@state)


Try to fix encoding, but not really successfull

--- server/testSystems/informatics/InformaticsSubmitDownloader.coffee
+++ server/testSystems/informatics/InformaticsSubmitDownloader.coffee
@@ -57,7 +57,8 @@
             [contest, run] = @parseRunId(runid)
             #href = "https://informatics.mccme.ru/moodle/ajax/ajax_file.php?objectName=source&contest_id=#{contest}&run_id=#{run}"
             href = "https://informatics.mccme.ru/py/problem/run/#{run}/source"
-            page = await @adminUser.download(href, {encoding: 'latin1'})
+            #page = await @adminUser.download(href, {encoding: 'latin1'})
+            page = await @adminUser.download(href)
             source = JSON.parse(page)?.data?.source || ""
             #document = (new JSDOM(page)).window.document
             #source = document.getElementById("source-textarea").innerHTML
@@ -78,7 +79,7 @@
             data = await @adminUser.download(href)
             logger.info "results data=", data
             result = JSON.parse(data)
-            if not result.tests?[1] and not result.message.includes('status="SV"') and not result.message.includes('status="CE"') and not result.protocol.includes('compile-error="yes"')
+            if not result.tests?[1] and not result.compiler_output and not result.message.includes('status="SV"') and not result.message.includes('status="CE"') and not result.protocol.includes('compile-error="yes"')
                 throw "No results found"
             return result
         catch e


put ace in readonly mode when changing session

if the session fails to attach successfully (e.g due to an exception in
the changeSession event handler) the editor will be left in a state
where the user will not be able to enter any text.  This should at least
cause them to reload the editor.

--- services/web/public/coffee/ide/editor/directives/aceEditor.coffee
+++ services/web/public/coffee/ide/editor/directives/aceEditor.coffee
@@ -356,6 +356,7 @@
 						session.setOption("useWorker", scope.syntaxValidation);

 					# now attach session to editor
+					editor.setReadOnly(true)
 					editor.setSession(session)

 					doc = session.getDocument()
@@ -364,6 +365,7 @@
 					editor.initing = true
 					sharejs_doc.attachToAce(editor)
 					editor.initing = false
+					editor.setReadOnly(false)

 					resetScrollMargins()


Matrix copy constructor

Matrix copy constructor

--- src/matrix.coffee
+++ src/matrix.coffee
@@ -50,7 +50,7 @@
   ###
   Matrix = (a, b, c, d, tx, ty) ->
     if Object.isObject(a)
-      {x, y} = a
+      {a, b, c, d, tx, } = a

     __proto__: Matrix::
     ###*


allow staff to set account level

allow staff to set account level

--- server/code/model/user.coffee
+++ server/code/model/user.coffee
@@ -109,6 +109,9 @@
       apikey: uuid.v4()
       accountLevel: 'free'

+    if opts.requestingUser.isStaff
+      newUser.accountLevel = opts.newUser.accountLevel or 'free'
+
     if opts.newUser.logoUrl?
       newUser.logoUrl = opts.newUser.logoUrl


Change email sender

Change email sender

--- server/lib/emailConfig.coffee
+++ server/lib/emailConfig.coffee
@@ -1,4 +1,4 @@
-Accounts.emailTemplates.from = "no-reply <no-reply@carddi.mobi>";
+Accounts.emailTemplates.from = "Eu Ajudo <no-reply@carddi.mobi>";

 if Meteor.settings.MAIL_URL?
 	console.log 'setting email', Meteor.settings.MAIL_URL


Credentials: show users helper

Credentials: show users helper

--- client/Account/views/credentials.coffee
+++ client/Account/views/credentials.coffee
@@ -187,6 +187,11 @@
     credential = item.getData()
     @emit "ShowShareCredentialFormFor", credential

+  showItemParticipants: (item)->
+
+    credential = item.getData()
+    credential.fetchUsers (err, users)->
+      info err, users

   showItemContent: (item)->

@@ -239,10 +244,16 @@
       disabled : !@getData().owner
       callback : => delegate.showItemContent this

+    @participantsButton = new KDButtonView
+      title    : "Show Participants"
+      cssClass : "solid small green"
+      callback : => delegate.showItemParticipants this
+
   pistachio:->
     """
      {h1{#(title)}} {span{#(provider)}}
      <div class='buttons'>
-      {{> @showCredentialButton}}{{> @deleteButton}}{{> @shareButton}}
+      {{> @showCredentialButton}}{{> @deleteButton}}
+      {{> @shareButton}}{{> @participantsButton}}
      </div>
     """


IDE: aceapplicationtabview to comply with the KDTabView

--- client/Ace/aceapplicationtabview.coffee
+++ client/Ace/aceapplicationtabview.coffee
@@ -2,15 +2,15 @@

   removePane_: KDTabView::removePane

-  removePane: (pane) ->
+  removePane: (pane, shouldDetach) ->
     {aceView} = pane.getOptions()

-    return  @removePane_ pane  unless aceView
+    return  @removePane_ pane, shouldDetach  unless aceView

     {ace} = aceView
     file  = ace.getData()

-    return @removePane_ pane  unless ace.isContentChanged()
+    return @removePane_ pane, shouldDetach  unless ace.isContentChanged()

     modal = new KDModalView
       width         : 620


webapp/latex: alert_message does not have a {message: ...} dict entry, but just a string

--- src/smc-webapp/editor_latex.coffee
+++ src/smc-webapp/editor_latex.coffee
@@ -416,7 +416,9 @@
                 @set_conf(resolution : res)
                 @preview.update()
             catch e
-                alert_message(type:"error", message:"Invalid resolution #{res}")
+                alert_message
+                    type    : "error"
+                    message : "Invalid resolution #{res}"

     get_resolution: () =>
         if not @preview?
@@ -621,7 +623,9 @@
     _show_error_in_file: (mesg, cb) =>
         file = mesg.file
         if not file
-            alert_message(type:"error", "No way to open unknown file.")
+            alert_message
+                type    : "error"
+                message : "No way to open unknown file."
             cb?()
             return
         if not mesg.line
@@ -634,7 +638,9 @@
                     resolution : @get_resolution()
                     cb         : cb
             else
-                alert_message(type:"error", "Unknown location in '#{file}'.")
+                alert_message
+                    type    : "error"
+                    message : "Unknown location in '#{file}'."
                 cb?()
                 return
         else
@@ -698,7 +704,9 @@
         opts.cb = (err, res) =>
             if err
                 if active
-                    alert_message(type:"error", message: "Inverse search error -- #{err}")
+                    alert_message
+                        type    : "error"
+                        message : "Inverse search error -- #{err}"
             else
                 if res.input != @filename
                     if active


CodeEditor: Instead getting the kite from vmController, rely on what the file have

--- client/DevTools/views/codeeditor.coffee
+++ client/DevTools/views/codeeditor.coffee
@@ -20,7 +20,7 @@

     file = FSHelper.createFileFromPath path

-    kite = KD.getSingleton('vmController').getKiteByVmName file.vmName
+    kite = file.getKite()
     return  callback {message: "VM not found"}  unless kite

     file.fetchContents (err, content)=>


Fix last merge

Fix last merge

--- server/stream/messages.coffee
+++ server/stream/messages.coffee
@@ -46,16 +46,23 @@
 		collection: RocketChat.models.Messages.model._name

 	MongoInternals.defaultRemoteCollectionDriver().mongo._oplogHandle.onOplogEntry query, (action) ->
+		record = undefined
+		type = undefined
+
+		if action.op.op is 'i'
+			record = action.op.o
+			type = 'inserted'
+			return
+
+		if action.op.op is 'u'
+			record = RocketChat.models.Messages.findOne({_id: action.id})
+			type = 'updated'
+			return
+
 		if record._hidden is true or record.imported?
 			return

-		if action.op.op is 'i'
-			publishMessage 'inserted', action.op.o
-			return
-
-		if action.op.op is 'u'
-			publishMessage 'updated', RocketChat.models.Messages.findOne({_id: action.id})
-			return
+		publishMessage type, record

 		# if action.op.op is 'd'
 		# 	publishMessage 'deleted', {_id: action.id}


component-lab: export Input main modules

Signed-off-by: Umut Sirin <4055abbc0e40a1254e715b952ccd414607e0b365@gmail.com>

--- client/component-lab/Input/index.coffee
+++ client/component-lab/Input/index.coffee
@@ -0,0 +1,2 @@
+module.exports = require './Input'
+module.exports.CreditCardInput = require './CreditCardInput'


Minor code refactor to acound jsmin flaw

--- app/components/vnc-viewer.coffee
+++ app/components/vnc-viewer.coffee
@@ -20,7 +20,7 @@

   didInsertElement: ->
     canvasEl = @element.getElementsByClassName("canvas")[0]
-    isPlatformIos = ENUMS.PLATFORM.IOS is @get "file.project.platform"
+    that = @
     @set "rfb", new RFB
       'target': canvasEl
       'encrypt': ENV.deviceFarmSsl
@@ -31,10 +31,12 @@
       'view_only': false

       'onUpdateState': ->
-        if isPlatformIos
-          display = @get_display()
-          scaleRatio = display.autoscale vncHeight, vncWidth  # TODO: This needs to be set Dynamically
-          @get_mouse().set_scale scaleRatio
+        if ENUMS.PLATFORM.IOS isnt that.get "file.project.platform"
+          # Only resize iOS Devices
+          return true
+        display = @get_display()
+        scaleRatio = display.autoscale vncHeight, vncWidth  # TODO: This needs to be set Dynamically
+        @get_mouse().set_scale scaleRatio
         true

       'onXvpInit': ->


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/level_state.coffee
+++ src/level_state.coffee
@@ -3,7 +3,7 @@

 @name LevelState
 @constructor
-@param {Number} duration T
+@param {Number} duration Amount of time
 @param {Number} period How fast the osciallator function repeats
 @param {Number} offset How much to offset the created osciallator function. Useful for translating between sin and cosine functions.
 ###


test/stacks: minor change

test/stacks: minor change

--- client/test/lib/dashboard/stacks.coffee
+++ client/test/lib/dashboard/stacks.coffee
@@ -53,6 +53,7 @@

     browser
       .pause 2000
+      .url stackEditorUrl
       .waitForElementVisible teamStacksSelector, 20000
       .waitForElementVisible stackTemplate, 20000


improved analytics tracking in options page and fixed some bugs

--- src/lib/options.coffee
+++ src/lib/options.coffee
@@ -113,27 +113,40 @@
       seconds = $(this).val()
       seconds = if seconds? then parseInt(seconds, 10) * 1000 else 0
       notifications.duration = seconds
+      analytics.track 'Notifications', 'Changed', 'Duration', seconds
   $('#notifications').change ->
     store.modify 'notifications', (notifications) =>
       notifications.enabled = $(this).is ':checked'
+      analytics.track 'Notifications', 'Changed', 'Enabled',
+        if notifications.enabled then 1 else 0

 # Bind the event handlers required for persisting general changes.
 loadSaveEvents = ->
   $('#analytics').change ->
-    store.set 'analytics', analytics = $(this).is ':checked'
-    if analytics
-      utils.addAnalytics()
-      chrome.extension.getBackgroundPage().utils.addAnalytics()
+    if $(this).is ':checked'
+      store.set 'analytics', yes
+      chrome.extension.getBackgroundPage().analytics.add()
+      analytics.add()
+      analytics.track 'General', 'Changed', 'Analytics', 1
     else
-      utils.removeAnalytics()
-      chrome.extension.getBackgroundPage().utils.removeAnalytics()
+      analytics.track 'General', 'Changed', 'Analytics', 0
+      analytics.remove()
+      chrome.extension.getBackgroundPage().analytics.remove()
+      store.set 'analytics', no
   bindSaveEvent '#anchorTarget, #anchorTitle', 'change', 'anchor', ->
     @is ':checked'
+  , (jel, key, value) ->
+    key = key[0].toUpperCase() + key.substr 1
+    analytics.track 'Anchors', 'Changed', key, if value then 1 else 0
   $('#contextMenu').change ->
-    store.set 'contextMenu', $(this).is ':checked'
+    store.set 'contextMenu', contextMenu = $(this).is ':checked'
     ext.updateContextMenu()
+    analytics.track 'General', 'Changed', 'Context Menu',
+      if contextMenu then 1 else 0
   $('#shortcuts').change ->
-    store.set 'shortcuts', $(this).is ':checked'
+    store.set 'shortcuts', shortcuts = $(this).is ':checked'
+    analytics.track 'General', 'Changed', 'Keyboard Shortcuts',
+      if shortcuts then 1 else 0

 # Update the templates section of the options page with the current settings.
 loadTemplates = ->
@@ -260,6 +273,7 @@
         updateToolbarTemplates()
         templates.change().focus()
         saveTemplates()
+        analytics.track 'Templates', 'Added', opt.text()
       else
         # Show the error messages to the user.
         $.facebox div: '#message'
@@ -273,9 +287,11 @@
   $('.delete_yes_btn').live 'click', ->
     opt = templates.find 'option:selected'
     if opt.data('readOnly') isnt 'true'
+      title = opt.text()
       opt.remove()
       templates.change().focus()
       saveTemplates()
+      analytics.track 'Templates', 'Deleted', title
     $(document).trigger 'close.facebox'
     updateToolbarTemplates()
   # Move the selected option down once when the *Down* control is clicked.
@@ -365,6 +381,7 @@

 # Bind the event handlers required for importing templates.
 loadTemplateImportEvents = ->
+  data      = null
   templates = $ '#templates'
   # Restore the previous view in the import process.
   $('.import_back_btn').live 'click', ->
@@ -412,9 +429,9 @@
   # Finalize the import process.
   $('.import_final_btn').live 'click', ->
     $this = $ this
-    list = $this.parents('.import_con_stage2').find '.import_con_list'
+    list  = $this.parents('.import_con_stage2').find '.import_con_list'
     list.find('option:selected').each ->
-      opt = $ this
+      opt         = $ this
       existingOpt = templates.find "option[value='#{opt.val()}']"
       opt.removeAttr 'selected'
       if existingOpt.length is 0
@@ -425,6 +442,9 @@
     updateToolbarTemplates()
     templates.focus()
     saveTemplates()
+    if data?
+      analytics.track 'Templates', 'Imported', data.version,
+        data.templates.length
   # Cancel the import process.
   $('.import_no_btn, .import_close_btn').live 'click', ->
     $(document).trigger 'close.facebox'
@@ -523,13 +543,21 @@
 # Bind the event handlers required for persisting toolbar behaviour changes.
 loadToolbarSaveEvents = ->
   $('input[name="toolbar_behaviour"]').change ->
+    popup = $('#toolbarPopup').is ':checked'
     store.modify 'toolbar', (toolbar) ->
-      toolbar.popup = $('#toolbarPopup').is ':checked'
+      toolbar.popup = popup
     ext.updateToolbar()
+    analytics.track 'Toolbars', 'Changed', 'Behaviour', if popup then 1 else 0
   bindSaveEvent '#toolbarClose, #toolbarKey, #toolbarStyle', 'change',
    'toolbar', (key) ->
     if key is 'key' then @val() else @is ':checked'
-  , ext.updateToolbar
+  , (jel, key, value) ->
+    ext.updateToolbar
+    key = key[0].toUpperCase() + key.substr 1
+    if key is 'Key'
+      analytics.track 'Toolbars', 'Changed', key
+    else
+      analytics.track 'Toolbars', 'Changed', key, if value then 1 else 0

 # Update the URL shorteners section of the options page with the current
 # settings.
@@ -564,11 +592,17 @@
 loadUrlShortenerSaveEvents = ->
   $('input[name="enabled_shortener"]').change ->
     store.modify 'bitly', 'googl', 'yourls', (data, key) ->
-      data.enabled = $("##{key}").is ':checked'
+      if data.enabled = $("##{key}").is ':checked'
+        shortener = ext.queryUrlShortener (shortener) ->
+          shortener.name is key
+        analytics.track 'Shorteners', 'Enabled', shortener.title
   bindSaveEvent '#bitlyApiKey, #bitlyUsername', 'input', 'bitly', ->
     @val().trim()
   bindSaveEvent '#googlOauth', 'change', 'googl', ->
     @is ':checked'
+  , (jel, key, value) ->
+    analytics.track 'Shorteners', 'Changed', 'goo.gl OAuth',
+      if value then 1 else 0
   bindSaveEvent "#yourlsPassword, #yourlsSignature, #yourlsUrl,
    #yourlsUsername", 'input', 'yourls', ->
     @val().trim()
@@ -757,6 +791,8 @@
     version:   ext.version
   for key in keys
     data.templates.push deriveTemplate $ "#templates option[value='#{key}']"
+  if data.templates.length
+    analytics.track 'Templates', 'Exported', ext.version, data.templates.length
   JSON.stringify data

 # Create a JSON object from the imported string specified.
@@ -854,16 +890,19 @@
       footer:
         opt_footer: "#{new Date().getFullYear()}"
     # Bind tab selection event to all tabs.
+    initialTabChange = yes
     $('[tabify]').click ->
       $this = $ this
       unless $this.hasClass 'selected'
         $this.siblings().removeClass 'selected'
         $this.addClass 'selected'
         $($this.attr 'tabify').show().siblings('.tab').hide()
-        id = $this.attr 'id'
-        store.set 'options_active_tab', id
-        id = id.match(/(\S*)_nav$/)[1]
-        # TODO: Analytics event tracking.
+        store.set 'options_active_tab', id = $this.attr 'id'
+        unless initialTabChange
+          id = id.match(/(\S*)_nav$/)[1]
+          id = id[0].toUpperCase() + id.substr 1
+          analytics.track 'Tabs', 'Changed', id
+        initialTabChange = no
     # Reflect the persisted tab.
     store.init 'options_active_tab', 'general_nav'
     $("##{store.get 'options_active_tab'}").click()
@@ -885,12 +924,12 @@
     $('#googlDeauthorize_btn').click ->
       store.remove key for key in googl.oauthKeys
       $(this).hide()
+      analytics.track 'Shorteners', 'Deauthorized', googl.title
     # Only show the "Revoke Access" button if Template has previously been
     # authorized by the [Google URL Shortener](http://goo.gl).
     for key in googl.oauthKeys when store.exists key
-      keyExists = yes
+      $('#googlDeauthorize_btn').show()
       break
-    $('#googlDeauthorize_btn').show() if keyExists
     # Bind event to template section items which will toggle the visibility of
     # its contents.
     $('.template-section').live 'click', ->
@@ -906,11 +945,9 @@
     for loc in LOCALES when loc is uiLocale
       locale = uiLocale
       break
-    $('#template_help').load(
-      chrome.extension.getURL("pages/templates_#{locale}.html"), ->
-        $('.template-section:first-child').click()
-        $('.version-replace').text ext.version
-    )
+    $('#template_help').load utils.url("pages/templates_#{locale}.html"), ->
+      $('.template-section:first-child').click()
+      $('.version-replace').text ext.version
     # Load the current option values.
     load()
     $('#template_shortcut_modifier').html if ext.isThisPlatform 'mac'
@@ -919,7 +956,8 @@
       ext.SHORTCUT_MODIFIERS
     # Initialize all faceboxes.
     $('a[facebox]').click ->
-      $.facebox div: $(this).attr 'facebox'
+      $.facebox div: id = $(this).attr 'facebox'
+      analytics.track 'Help', 'Opened', id.substr 1
     $(document).bind 'reveal.facebox', ->
       facebox = $ '#facebox > .popup > .content'
       facebox.css 'margin-right',


fix typo in latest build artifacts api doc

--- resources/assets/js/outer/api_data.coffee
+++ resources/assets/js/outer/api_data.coffee
@@ -244,7 +244,7 @@
         """
     artifacts_latest:
       url: "/api/v1/project/:username/:project/latest/artifacts"
-      description: "List the artifacts produced by the latest build on a given branch. Filter "
+      description: "List the artifacts produced by the latest build on a given branch."
       method: "GET"
       params: [
           name: "branch"


Rename class var @treeView to @instance

Rename class var @treeView to @instance

--- src/extensions/tree-view/tree-view.coffee
+++ src/extensions/tree-view/tree-view.coffee
@@ -14,14 +14,14 @@
     requireStylesheet 'tree-view.css'

     if state
-      @treeView = TreeView.deserialize(state, rootView)
+      @instance = TreeView.deserialize(state, rootView)
     else
-      @treeView = new TreeView(rootView)
-
-    rootView.horizontal.prepend(@treeView)
+      @instance = new TreeView(rootView)
+
+    rootView.horizontal.prepend(@instance)

   @serialize: ->
-    @treeView.serialize()
+    @instance.serialize()

   @content: (rootView) ->
     @div class: 'tree-view', tabindex: -1, =>


Clean up replacer functions

Clean up replacer functions

--- src/parser.coffee
+++ src/parser.coffee
@@ -26,7 +26,7 @@
     @content = @content.replace /(#)/g, "\\$1"

     # tag all "On...wrote:" occurrences with # symbol
-    replacer = (match, offset, string) =>
+    replacer = (match) =>
       match = match.replace /\r?\n/g, " "
       # remove any unwanted characters and strings
       match = match.replace /([\[\]<>]|mailto:|javascript:;)/g, ""
@@ -81,7 +81,7 @@
       (.*\n){1,2}
     ///g

-    replacer = (match, offset, string) =>
+    replacer = (match) =>
       match = match.replace /\r?\n/g, " "
       from = (/From:(.*?)(To|Subject|Date|Cc|Sent):/g.exec match)[1].trim()
       date = (/(Date|Sent):(.*?)(To|Subject|Cc):/g.exec match)[2].trim()


remove unused code

remove unused code

--- tests.coffee
+++ tests.coffee
@@ -2935,9 +2935,6 @@

 Tinytest.addAsync 'Neo4jDB - complex queries - see #31', (test, completed) ->
   db.query("WITH TIMESTAMP() AS timestamp CREATE (node:Group {name: 'name1'}) RETURN node").each (node) ->
-    # data = node.node.get()
-    # test.equal(data.name, 'name1')
-    # test.equal(data.labels, ['Group'])
     __nodeCRC__ test, node.node.get(), ["Group"], {name: 'name1'}
     test.equal node.node.remove(), undefined
     completed()


Remove tooltip is closed

Remove tooltip is closed

Fixes #6

--- lib/blame-gutter-view.coffee
+++ lib/blame-gutter-view.coffee
@@ -24,18 +24,18 @@
     if @visible
       @update()

-      @tooltips ?= new CompositeDisposable
-
-      @subscriptions ?= new CompositeDisposable
-      @subscriptions.add @editor.onDidSave => @update()
+      @disposables ?= new CompositeDisposable
+      @disposables.add @editor.onDidSave => @update()

       @gutter.show()

     else
       @gutter.hide()
+
+      @disposables?.dispose()
+      @disposables = null
+
       @removeAllMarkers()
-      @subscriptions?.dispose()
-      @subscriptions = null

   update: () ->

@@ -137,7 +137,6 @@
   removeAllMarkers: ->
     marker.destroy() for marker in @markers
     @markers = []
-    @tooltips.dispose()

   resizeStarted: (e) =>
     document.addEventListener 'mousemove', @resizeMove
@@ -189,10 +188,10 @@
       msgItem = document.createElement('div')
       msgItem.classList.add 'blame-tooltip'

-      @tooltips.add atom.tooltips.add item, title: msgItem
-
-      getCommit @editor.getPath(), hash.replace(/^[\^]/, ''), (msg) =>
-        avatar = gravatar.url(msg.email, {s: 80});
+      @disposables.add atom.tooltips.add item, title: msgItem
+
+      getCommit @editor.getPath(), hash.replace(/^[\^]/, ''), (msg) ->
+        avatar = gravatar.url(msg.email, { s: 80 })
         msgItem.innerHTML = """

@@ -203,7 +202,6 @@
         """

   deactivate: ->
-    @subscriptions?.dispose()
-    @tooltips?.dispose()
+    @disposables?.dispose()

 module.exports = BlameGutterView


Don't render results when app isn't open

--- app/scripts/views/results.coffee
+++ app/scripts/views/results.coffee
@@ -40,7 +40,7 @@

     render: ->
       @$el.empty()
-      return unless @commandViews.length
+      return unless @commandViews.length and App.open
       $ul = $ "<ul>"
       for view in @commandViews
         view.render() unless view.isRendered()


Decruft

Decruft

--- src/main/assets/javascripts/simple.coffee
+++ src/main/assets/javascripts/simple.coffee
@@ -1,40 +1,3 @@
-  # locations = []
-  # polyline = null
-#
-  # addLocation = (coords) ->
-    # locations.push
-      # datetime: new Date
-      # coords: coords
-#
-    # drawPolyline()
-#
-  # drawPolyline = ->
-    # map.removeLayer(polyline) unless !polyline
-#
-    # latlngs = locations.map (location) ->
-      # return new L.LatLng location.coords.latitude, location.coords.longitude
-#
-    # polyline = L.polyline(latlngs, {color: 'red'}).addTo(map)
-#
-  # cachedLat = -37.814053
-  # cachedLong = 144.963183
-#
-  # detectLocation = ->
-    # addLocation
-      # latitude: cachedLat
-      # longitude: cachedLong
-#
-    # if(Math.random() > 0.5)
-      # cachedLat = cachedLat + Math.random() * 0.003 * -1
-    # else
-      # cachedLat = cachedLat + Math.random() * 0.003
-    # if(Math.random() > 0.5)
-      # cachedLong = cachedLong + Math.random() * 0.003 * -1
-    # else
-      # cachedLong = cachedLong + Math.random() * 0.003
-
-# setInterval detectLocation, 1000
-
 generateData = ->
   data = [
     {


Updated pubsub to match the updated dialect.

--- src/pubsub.coffee
+++ src/pubsub.coffee
@@ -40,10 +40,10 @@
         @sequence += 1

         record =
-          sequence: seq
-          directive: 'publish'
-          channel: channel
-          message: message
+          seq: seq
+          action: 'publish'
+          chan: channel
+          msg: message

         @sock.send JSON.stringify(record)
         .then =>
@@ -68,8 +68,8 @@
         @sequence += 1

         record =
-          sequence: seq
-          directive: 'subscribe'
+          seq: seq
+          action: 'subscribe'
           channel: channel

         @sock.send JSON.stringify(record)
@@ -95,8 +95,8 @@
         @sequence += 1

         record =
-          sequence: seq
-          directive: 'unsubscribe'
+          seq: seq
+          action: 'unsubscribe'
           channel: channel

         @sock.send JSON.stringify(record)


Adds initial #each() unit tests

Adds initial #each() unit tests

--- test/database-spec.coffee
+++ test/database-spec.coffee
@@ -18,14 +18,16 @@
     database.connect 'foo', (err, db) ->
       #console.log db
       #assert.fail()
-      done()
+      db.close ->
+        done()

   it 'should connect to default database if none provided', (done) ->
     database.connect null, (err, db) ->
       assert.equal err, null, 'err should be null'
       assert.notEqual db, null, 'db should not be null'
       assert.equal db.databaseName, 'ntb_07', 'database should be ntb_07'
-      done()
+      db.close ->
+        done()

   it 'should not suprepress runtime errors', (done) ->
     database.connect null, (err, db) ->
@@ -34,5 +36,49 @@
       catch e
         assert e instanceof Error, 'e should be an instance of error'
         assert /ReferenceError: foobar is not defined/.test(e), 'e should be ReferenceError'
+        db.close ->
+          done()
+
+describe.only '#each()', ->
+  db = col = null
+
+  before (done) ->
+    database.connect null, (err, database) ->
+      throw err if err
+      db = database
+      db.collection 'turer', (err, collection) ->
+        throw err if err
+        col = collection
         done()

+  after (done) -> db.close -> done()
+
+  it 'should itterate over cursor', (done) ->
+    this.timeout = 2000
+
+    cursor = col.find()
+    counter = 0
+
+    database.each cursor, (doc, i, count, cb) ->
+      counter++
+      cb()
+    , (err, i, count) ->
+      throw err if err
+      assert.equal counter, count
+      assert.equal i+1, count
+      done()
+
+  it 'should ittereate over cursor with limit', (done) ->
+    cursor  = col.find().limit(10)
+    counter = 0
+
+    database.each cursor, (doc, i, count, cb) ->
+      counter++
+      cb()
+    , (err, i, count) ->
+      throw err if err
+      assert.equal counter, 10
+      assert.equal i, 9
+      assert.equal count, 10
+      done()
+


ComputeProviders: StackTemplate: Set "shouldFetchGroupPlan" no

--- workers/social/lib/social/models/computeproviders/stacktemplate.coffee
+++ workers/social/lib/social/models/computeproviders/stacktemplate.coffee
@@ -295,7 +295,7 @@

       shouldReviveClient   : yes
       shouldReviveProvider : no
-      shouldFetchGroupPlan : yes
+      shouldFetchGroupPlan : no

     , (client, accessLevel, callback) ->


Got excited when all tests passed CI. It was because only a few were running.

--- test/hls_segmenter.coffee
+++ test/hls_segmenter.coffee
@@ -374,7 +374,7 @@

     # now put it all together. This time, create an HLSSegmenter and a
     # RewindBuffer and feed audio through its normal course.
-    describe.only "RewindBuffer -> Segmenter", ->
+    describe "RewindBuffer -> Segmenter", ->
         rewind      = null
         generator   = null
         r2          = null


Change `markers?.length` into `markers.length`

The existence check is no longer needed, since `injectHints` now always
returns an array: Where it before returned `undefined`, it now returns an
empty array.

--- extension/packages/commands.coffee
+++ extension/packages/commands.coffee
@@ -51,7 +51,7 @@
 # Copy element URL to the clipboard
 command_yf = (vim) ->
   markers = hints.injectHints(vim.window.document)
-  if markers?.length > 0
+  if markers.length > 0
     cb = (marker) ->
       if url = marker.element.href
         marker.element.focus()
@@ -64,7 +64,7 @@
 # Focus element
 command_vf = (vim) ->
   markers = hints.injectHints(vim.window.document)
-  if markers?.length > 0
+  if markers.length > 0
     vim.enterHintsMode(markers, (marker) -> marker.element.focus())

 # Copy current URL to the clipboard
@@ -198,7 +198,7 @@
 command_f = (vim) ->
   if document = vim.window.document
     markers = hints.injectHints(document)
-    if markers?.length > 0
+    if markers.length > 0
       # This callback will be called with the selected marker as argument
       cb = (marker) ->
         marker.element.focus()
@@ -209,7 +209,7 @@
 # Follow links in a new Tab with hint markers
 command_F = (vim) ->
   markers = hints.injectHints(vim.window.document)
-  if markers?.length > 0
+  if markers.length > 0
     # This callback will be called with the selected marker as argument
     cb = (marker) ->
       marker.element.focus()


Update the Rivets.View code documentation.

--- src/rivets.coffee
+++ src/rivets.coffee
@@ -65,9 +65,9 @@
     if @bindType is "bidirectional"
       @el.removeEventListener 'change', @publish

-# A collection of bindings for a parent element.
+# A collection of bindings built from a set of parent elements.
 class Rivets.View
-  # The parent DOM element and the model objects for binding are passed into the
+  # The DOM elements and the model objects for binding are passed into the
   # constructor.
   constructor: (@els, @models) ->
     @els = [@els] unless (@els.jquery || @els instanceof Array)


layout

layout

--- src/routes.coffee
+++ src/routes.coffee
@@ -26,7 +26,10 @@
   theHub = req.app.hub
   theFeed.unshift activity
   theHub.distribute activity, localURL("feed.json"), (err) ->
-
+    if err
+      console.error err
+    else
+      console.dir {message: "distributed", activity: activity.id}
   res.writeHead 201
   res.end()

@@ -43,6 +46,7 @@
     "<" + localURL("hub") + ">; rel=\"hub\""
     "<" + localURL("feed.json") + ">; rel=\"self\""
   ]
+
   res.writeHead 200,
     "Content-Type": "application/json"

@@ -51,7 +55,6 @@
 exports.publish = (req, res) ->
   res.render "publish",
     title: "Help for publishers"
-

 exports.subscribe = (req, res) ->
   res.render "subscribe",
@@ -71,7 +74,7 @@
     else
       namespace = "__default__"
       name = dotted
-    params[namespace] = {}  unless params.hasOwnProperty(namespace)
+    params[namespace] = {} unless params.hasOwnProperty(namespace)
     params[namespace][name] = body[dotted]
   params


Switch that dropdown icon when we toggle things.

--- base/assets/javascripts/base.coffee
+++ base/assets/javascripts/base.coffee
@@ -25,7 +25,6 @@

 # Happenings-related calls.
 ($ document).ready ->
-  # ($ '.happening-toggle').click ->
   ($ document).on 'click', '.happening-toggle', ->
-    console.log 'what'
     ($ '.happening-decade-list').toggleClass 'visible'
+    ($ '.happening-toggle .ss-icon:last-child').toggleClass 'ss-dropdown ss-directup'


Add additional status to bundle.coffee

Add additional status to bundle.coffee

--- public/assets/coffee/bundle.coffee
+++ public/assets/coffee/bundle.coffee
@@ -76,6 +76,8 @@
     53:'배송중'
     54:'배송완료'
     55:'반송신청'
+    56:'입금대기'
+    57:'재활용(비전)'
     color:
       1:  'label-success'
       2:  'label-danger'


Set DNT setting on middleware

Set DNT setting on middleware

--- lib/middleware/locals.coffee
+++ lib/middleware/locals.coffee
@@ -12,6 +12,10 @@
   res.locals._ = _
   res.locals.homeHref = req.user?.homePath() or '/'

+  # respect do not track headers
+  res.locals.doNotTrack = res.locals.sd.DO_NOT_TRACK = req.headers.dnt
+
+
   # TODO: remove after campaign
   # only show if the user is logged in and confirmed
   # and hasn't closed the CTA


Fixes #175

Fixes #175

--- lib/models/git-branch.coffee
+++ lib/models/git-branch.coffee
@@ -32,7 +32,7 @@
     @disposables.add atom.commands.add 'atom-text-editor', 'core:confirm': (event) =>
       editor = @branchEditor.getModel()
       name = editor.getText()
-      if name.length a 0
+      if name.length > 0
         @createBranch name
         destroy()


clean up code

clean up code

--- lib/notifications-log.coffee
+++ lib/notifications-log.coffee
@@ -22,16 +22,12 @@
     })

   render: ->
-
-    # Create root element
     @element = document.createElement('div')
     @element.classList.add('notifications-log')

-    # Add Header
     header = document.createElement('header')
     @element.appendChild(header)

-    # Add Buttons
     for type, icon of typeIcons
       button = document.createElement('button')
       button.classList.add('notification-type', 'btn', 'icon', "icon-#{icon}", 'show-type', type)
@@ -40,12 +36,10 @@
       @disposables.add atom.tooltips.add(button, {title: "Toggle #{type} notifications"})
       header.appendChild(button)

-    # Add Container
     @list = document.createElement('ul')
     @list.classList.add('notifications-log-items')
     @element.appendChild(@list)

-    # Add Notifications
     lastNotification = null
     for notification in atom.notifications.getNotifications()
       if lastNotification?


avoid leak when calling chai.should() repeatedly in smoke test

--- test/smoke/coffee/SmokeTests.coffee
+++ test/smoke/coffee/SmokeTests.coffee
@@ -1,5 +1,5 @@
 chai = require("chai")
-chai.should()
+chai.should() unless Object.prototype.should?
 expect = chai.expect
 request = require "request"
 Settings = require "settings-sharelatex"


server: also server js

server: also server js

--- src/server.coffee
+++ src/server.coffee
@@ -15,6 +15,12 @@
 app.get '/evaluate.html', (req, res) ->
   res.sendfile './example/evaluate.html'

+app.get '/dst/parser.js', (req, res) ->
+  res.sendfile './dst/parser.js'
+
+app.get '/dst/evaluator.js', (req, res) ->
+  res.sendfile './dst/evaluator.js'
+
 app.get '/test', (req, res) ->
   formula = req.query.formula
   data = req.query.data


#242 use clipDirection instead of clip for screenPosition clipping

--- lib/selection-wrapper.coffee
+++ lib/selection-wrapper.coffee
@@ -36,13 +36,12 @@

   getNormalizedBufferPosition: ->
     point = @selection.getHeadBufferPosition()
-    editor = @selection.editor
     if @isForwarding()
-      screenPoint = editor.screenPositionForBufferPosition(point)
-      point = editor.bufferPositionForScreenPosition screenPoint.translate([0, -1]),
-        clip: 'backward'
-        wrapBeyondNewlines: true
-    point
+      {editor} = @selection
+      screenPoint = editor.screenPositionForBufferPosition(point).translate([0, -1])
+      editor.bufferPositionForScreenPosition(screenPoint, clipDirection: 'backward')
+    else
+      point

   # Return function to dispose(=revert) normalization.
   normalizeBufferPosition: ->
@@ -129,9 +128,9 @@
       {editor} = @selection
       start = @selection.getTailScreenPosition()
       if @selection.isReversed()
-        end = editor.clipScreenPosition(start.translate([0, -1]), {clip: 'backward'})
+        end = editor.clipScreenPosition(start.translate([0, -1]), clipDirection: 'backward')
       else
-        end = editor.clipScreenPosition(start.translate([0, +1]), {clip: 'forward', wrapBeyondNewlines: true})
+        end = editor.clipScreenPosition(start.translate([0, +1]), clipDirection: 'forward')
       editor.bufferRangeForScreenRange([start, end])

   preserveCharacterwise: ->
@@ -177,10 +176,8 @@
     [start.row, end.row] = @selection.getBufferRowRange()

     editor = @selection.editor
-    screenPoint = editor.screenPositionForBufferPosition(end)
-    end = editor.bufferPositionForScreenPosition screenPoint.translate([0, +1]),
-      clip: 'forward'
-      wrapBeyondNewlines: true
+    screenPoint = editor.screenPositionForBufferPosition(end).translate([0, 1])
+    end = editor.bufferPositionForScreenPosition(screenPoint, clipDirection: 'forward')

     @setBufferRange([start, end], {preserveFolds: true})
     @resetProperties()


rewrite aspect ratio code

rewrite aspect ratio code

mostly works, there is still an error in the position of
the axes' labels (they are too close to the frame)

--- src/math3d.coffee
+++ src/math3d.coffee
@@ -198,7 +198,7 @@
             background      : [1,1,1]
             spin            : false     # if true, image spins by itself when mouse is over it.
             camera_distance : 10
-            aspect_ratio    : undefined # undefined does nothing or a triple [x,y,z] of length three, which scales the x,y,z coordinates of everything by the given values.
+            aspect_ratio    : [1, 1, 1] # a triple [x,y,z] of length three, which scales the x,y,z coordinates of everything by the given values.
             stop_when_gone  : undefined # if given, animation, etc., stops when this html element (not jquery!) is no longer in the DOM
             frame           : undefined # frame options
             callback        : undefined # opts.callback(error, this object)
@@ -218,8 +218,22 @@
                 return @opts.callback? undefined, @
             @_init = true

-            @init_aspect_ratio_functions()
-
+            # setup aspect ratio stuff
+            aspectRatio = @aspectRatio = new THREE.Vector3 @opts.aspect_ratio...
+            @squareScale = (new THREE.Vector3 1, 1, 1).normalize()
+            @squareScale.multiplyScalar @aspectRatio.length()
+            @rescale = (vector) -> vector.multiply aspectRatio
+
+            # setup color stuff
+            @background = new THREE.Color @opts.background...
+            @element.style.background = @background.getStyle()
+            if @frameOpts.color?
+                @frameColor = new THREE.Color @frameOpts.color...
+            else
+                @frameColor = new THREE.Color(
+                    1-@background.r, 1-@background.g, 1-@background.b)
+
+            # initialize the scene
             @scene = new THREE.Scene()

             # IMPORTANT: There is a major bug in three.js -- if you make the width below more than .5 of the window
@@ -238,16 +252,6 @@
             # add a bunch of lights
             @init_light()

-            # set background color
-            @background = new THREE.Color @opts.background...
-            @element.style.background = @background.getStyle()
-
-            if @frameOpts.color?
-                @frameColor = new THREE.Color @frameOpts.color...
-            else
-                @frameColor = new THREE.Color(
-                    1-@background.r, 1-@background.g, 1-@background.b)
-
             @opts.callback? undefined, @

     attach_to_dom: (parent_element) ->
@@ -262,6 +266,19 @@
     # client code should call this when done adding objects to the scene
     finalize: ->
         @set_frame()
+
+        @_center = @rescale @boundingBox.geometry.boundingBox.center()
+
+        @camera.lookAt @_center
+        @controls.target = @_center
+
+        dim = @rescale @boundingBox.geometry.boundingBox.size()
+
+        maxDim = Math.max dim.x, dim.y, dim.z
+
+        @camera.position.set 1.5, 1.5, 0.75
+        @camera.position.multiplyScalar(maxDim).add @_center
+
         @render_scene()

         # possibly show the canvas warning.
@@ -331,16 +348,6 @@
         @element.onclick = =>
             @setDynamicRenderer()

-    # initialize functions to create new vectors, which take into account the scene's 3d frame aspect ratio.
-    init_aspect_ratio_functions: ->
-        if @opts.aspect_ratio?
-            [x, y, z] = @opts.aspect_ratio
-            @aspectRatio = new THREE.Vector3 @opts.aspect_ratio...
-            @vector = (a, b, c) -> (new THREE.Vector3 a, b, c).multiply(@aspectRatio)
-        else
-            @aspectRatio = new THREE.Vector3 1, 1, 1
-            @vector = (a, b, c) -> new THREE.Vector3 a, b, c
-
     data_url: (opts) ->
         opts = defaults opts,
             type    : 'png'      # 'png' or 'jpeg' or 'webp' (the best)
@@ -356,8 +363,6 @@
         @controls.damping = 2
         @controls.noKeys = true
         @controls.zoomSpeed = 0.4
-        if @_center?
-            @controls.target = @_center
         if @opts.spin
             if typeof @opts.spin is "boolean"
                 @controls.autoRotateSpeed = 2.0
@@ -368,12 +373,18 @@
         up = new THREE.Vector3()
         @controls.addEventListener 'change', =>
             if @renderer_type is 'dynamic'
-                @rescaleObjects()
+
+                if @_center? and @_points?
+                    scale = @camera.position.distanceTo @_center
+                    for point in @_points
+                        point.scale.set scale, scale, scale
+
                 if @_text?
                     up.set(0, 1, 0).applyQuaternion @camera.quaternion
                     for mesh in @_text
                         mesh.up = up.clone()
                         mesh.lookAt @camera.position
+
                 @renderer.render @scene, @camera

     add_camera: (opts) ->
@@ -420,9 +431,13 @@
         @boundingBox.update @boundingBox

     _finalizeObj: (obj, in_frame) ->
+        obj.scale.copy @aspectRatio
+
         if in_frame
             @updateBoundingBox obj
+
         @scene.add obj
+
         return obj

     addText: (opts) ->
@@ -467,7 +482,7 @@
         material.color.setRGB opts.texture.color...

         text = new THREE.Mesh geometry, material
-        text.position.copy(@vector opts.loc...)
+        @rescale text.position.set(opts.loc...)
         text.rotation.set opts.rotation...

         geometry.computeBoundingBox()
@@ -486,7 +501,9 @@

             @matrixWorldNeedsUpdate = true

-        return @_finalizeObj text, opts.in_frame
+        @_finalizeObj text, opts.in_frame
+        text.scale.copy @squareScale
+        return text

     addLine: (opts) ->
         opts = defaults opts,
@@ -498,12 +515,12 @@

         geometry = new THREE.Geometry()
         for point in opts.points
-            geometry.vertices.push @vector(point...)
+            geometry.vertices.push new THREE.Vector3(point...)

         line = new THREE.Line geometry, new THREE.LineBasicMaterial(linewidth:opts.thickness)
         line.material.color.setRGB opts.texture.color...

-        return @_finalizeObj line
+        return @_finalizeObj line, opts.in_frame

     addSphere: (opts) ->
         opts = defaults opts,
@@ -532,7 +549,7 @@
             material.opacity          = opts.texture.opacity

         sphere = new THREE.Mesh geometry, material
-        sphere.position.copy(@vector opts.loc...)
+        @rescale sphere.position.set(opts.loc...)

         return @_finalizeObj sphere, opts.in_frame

@@ -541,7 +558,7 @@
             loc         : [0,0,0]
             size        : 5
             texture     : required
-            use_cloud   : false
+            use_cloud   : false     # faster, but you have to use square points
             in_frame    : true

         if opts.use_cloud
@@ -557,9 +574,10 @@
                 material.color.setRGB opts.texture.color...
                 cloud = (@_cloud[key] = new THREE.PointCloud(
                                             new THREE.Geometry(), material))
+                cloud.scale.copy @aspectRatio
                 @scene.add cloud

-            cloud.geometry.vertices.push @vector(opts.loc...)
+            cloud.geometry.vertices.push new THREE.Vector3(opts.loc...)

             if opts.in_frame
                 @updateBoundingBox cloud
@@ -571,7 +589,7 @@
             in_frame = opts.in_frame
             loc = opts.loc

-            opts.radius = opts.size/400
+            opts.radius = opts.size/1200
             opts._basic_material = true
             opts.in_frame = false

@@ -585,9 +603,9 @@
             if in_frame
                 if not @_pointHelper?
                     @_pointHelper = new THREE.Mesh()
-                    @_pointHelper.geometry.vertices.push undefined
-
-                @_pointHelper.geometry.vertices[0] = @vector loc...
+                    @_pointHelper.geometry.vertices.push new THREE.Vector3()
+
+                @_pointHelper.geometry.vertices[0].set loc...
                 @updateBoundingBox @_pointHelper
             return point

@@ -602,7 +620,7 @@
         geometry = new THREE.Geometry()

         for vector in opts.vertices
-            geometry.vertices.push @vector(vector...)
+            geometry.vertices.push new THREE.Vector3(vector...)

         for vertex in opts.faces
             a = vertex.shift()
@@ -694,21 +712,6 @@
             z0 -= 1
         ###

-        min = @boundingBox.geometry.boundingBox.min
-        max = @boundingBox.geometry.boundingBox.max
-        avg = @boundingBox.geometry.boundingBox.center()
-        dim = @boundingBox.geometry.boundingBox.size()
-
-        @_center = avg
-
-        if @camera?
-            d = 1.5*Math.max(dim.x, dim.y, dim.z)
-            @camera.position.set avg.x+d, avg.y+d, avg.z+d/2
-
-        @camera.lookAt @_center
-        if @controls?
-            @controls.target = @_center
-
         if @frameOpts.thickness isnt 0 and not @_bounded
             @_bounded = true

@@ -716,32 +719,49 @@
             @boundingBox.material.color = @frameColor
             @boundingBox.material.linewidth = @frameOpts.thickness

+            # set the scale for the bounding box
+            @boundingBox.scale.copy @aspectRatio
+
+            # the update method of BoxHelper disables matrixAutoUpdate but
+            # we still need it for the aspect ratio to be taken into account
+            @boundingBox.matrixAutoUpdate = true
+
             # add the bounding box to the scene
             @scene.add @boundingBox

             if @frameOpts.labels and not @_labeled
                 @_labeled = true

-                maxDim = Math.max(dim.x, dim.y, dim.z)
-                textSize = Math.min(dim.x, dim.y, dim.z)/10
+                min = @boundingBox.geometry.boundingBox.min
+                max = @boundingBox.geometry.boundingBox.max
+                avg = @boundingBox.geometry.boundingBox.center()
+
+                dim = @rescale @boundingBox.geometry.boundingBox.size()
+
+                maxDim = Math.max dim.x, dim.y, dim.z
+                minDim = Math.min dim.x, dim.y, dim.z
+
+                offset = maxDim*0.05
+                offsets = (new THREE.Vector3 offset, offset, offset).divide @aspectRatio
+
+                textSize = minDim/@squareScale.length()/6

                 if textSize is 0
                     return

                 frameColor = [@frameColor.r, @frameColor.g, @frameColor.b]
-                offset = maxDim*0.05

                 addHashMark = (loc) =>
                     loc2 = new THREE.Vector3 loc...
-                    if offsetDirection[0] is '+'
-                        loc2[offsetDirection[1]] += offset*0.75
-                    else if offsetDirection[0] is '-'
-                        loc2[offsetDirection[1]] -= offset*0.75
+                    if offsetDir[0] is '+'
+                        loc2[offsetDir[1]] += offsets[offsetDir[1]]*0.75
+                    else if offsetDir[0] is '-'
+                        loc2[offsetDir[1]] -= offsets[offsetDir[1]]*0.75
                     loc2 = [loc2.x, loc2.y, loc2.z]

                     @addLine
                             points     : [loc, loc2]
-                            thickness  : @frameOpts.thickness*4
+                            thickness  : @frameOpts.thickness*5
                             in_frame   : false
                             texture    :
                                     color   : frameColor
@@ -760,33 +780,33 @@
                                     opacity : 1

                     # add a bit of extra offset based on the size of the text
-                    textBox = text.geometry.boundingBox.size()
+                    # TODO: this is still not right for aspect ratio != identity
+                    textBox = text.geometry.boundingBox.size().multiply @squareScale
                     extraOffset = Math.max(textBox.x, textBox.y, textBox.z)*0.5

-                    realOffset = offset + extraOffset
-                    if offsetDirection[0] is '+'
-                        text.position[offsetDirection[1]] += realOffset
-                    else if offsetDirection[0] is '-'
-                        text.position[offsetDirection[1]] -= realOffset
-
-                format = (vec, coord) =>
-                    num = vec[coord]/@aspectRatio[coord]
+                    realOffset = offsets[offsetDir[1]] + extraOffset
+                    if offsetDir[0] is '+'
+                        text.position[offsetDir[1]] += realOffset
+                    else if offsetDir[0] is '-'
+                        text.position[offsetDir[1]] -= realOffset
+
+                format = (num) ->
                     Number(num.toFixed 2).toString()

-                offsetDirection = ['-','y']
-                addLabel [max.x, min.y, min.z], format(min, 'z')
-                addLabel [max.x, min.y, avg.z], "z = #{format avg, 'z'}"
-                addLabel [max.x, min.y, max.z], format(max, 'z')
-
-                offsetDirection = ['+','x']
-                addLabel [max.x, min.y, min.z], format(min, 'y')
-                addLabel [max.x, avg.y, min.z], "y = #{format avg, 'y'}"
-                addLabel [max.x, max.y, min.z], format(max, 'y')
-
-                offsetDirection = ['+','y']
-                addLabel [max.x, max.y, min.z], format(max, 'x')
-                addLabel [avg.x, max.y, min.z], "x = #{format avg, 'x'}"
-                addLabel [min.x, max.y, min.z], format(min, 'x')
+                offsetDir = ['-','y']
+                addLabel [max.x, min.y, min.z], format(min.z)
+                addLabel [max.x, min.y, avg.z], "z = #{format avg.z}"
+                addLabel [max.x, min.y, max.z], format(max.z)
+
+                offsetDir = ['+','x']
+                addLabel [max.x, min.y, min.z], format(min.y)
+                addLabel [max.x, avg.y, min.z], "y = #{format avg.y}"
+                addLabel [max.x, max.y, min.z], format(max.y)
+
+                offsetDir = ['+','y']
+                addLabel [max.x, max.y, min.z], format(max.x)
+                addLabel [avg.x, max.y, min.z], "x = #{format avg.x}"
+                addLabel [min.x, max.y, min.z], format(min.x)

     animate: (opts = {}) ->
         opts = defaults opts,
@@ -859,25 +879,7 @@
         if not new_pos and not force
             return

-        # rescale all objects in scene
-        @rescaleObjects()
-
         @renderer.render @scene, @camera
-
-    _rescaleFactor: ->
-        if not @_center?
-            return undefined
-        else
-            return @camera.position.distanceTo(@_center) / 3
-
-    rescaleObjects: (force = false) ->
-        scale = @_rescaleFactor()
-        if not scale? or (Math.abs(@_lastScale - scale) < 0.000001 and not force)
-            return
-        if @_points?
-            for z in @_points
-                z.scale.set scale, scale, scale
-        @_lastScale = scale

 math3d.render_3d_scene = (opts) ->
     opts = defaults opts,


Only start execution after the file has been saved

--- lib/workspace-manager.coffee
+++ lib/workspace-manager.coffee
@@ -354,10 +354,10 @@
       activeTextEditor = activeLevelCodeEditor.getTextEditor()
       activeTerminal = activeLevelCodeEditor.getTerminal()
       if (filePath = activeTextEditor.getPath() ? atom.showSaveDialogSync())?
-        activeTextEditor.saveAs(filePath)
-        activeLevelCodeEditor.startExecution()
-        activeTerminal.show()
-        activeTerminal.focus()
+        activeTextEditor.saveAs(filePath).then () =>
+          activeLevelCodeEditor.startExecution()
+          activeTerminal.show()
+          activeTerminal.focus()
     else
       event.abortKeyBinding()


make trust_ca_cert_paths optional

make trust_ca_cert_paths optional

--- trust.coffee
+++ trust.coffee
@@ -6,8 +6,9 @@

 trust_ca_certs = (settings, callback) ->
+  {trust_charles, trust_ca_cert_paths} = settings
+  return callback null if not trust_ca_cert_paths
   return callback null if settings.device_udid
-  {trust_charles, trust_ca_cert_paths} = settings
   return callback null if trust_ca_cert_paths.length == 0 and not trust_charles
   truststore_path = "#{process.env.HOME}/Library/Application\ Support/iPhone\ Simulator/5.1/Library/Keychains/TrustStore.sqlite3"


Removed console.log in gamespec.coffee

Removed console.log in gamespec.coffee

--- spec/gamespec.coffee
+++ spec/gamespec.coffee
@@ -61,7 +61,6 @@
         game.player1.addToLine(new Position([0, 1], Game.NORTH, 0))
         SynchronizedTime.setTimeForTesting(1)
         game.timer_tick()
-        console.log(c.args for c in listener.notify.calls)
         expect(listener.notify).toHaveBeenCalledWith(game.player1, game.player0, new Point(0,0))
         expect(listener.notify).not.toHaveBeenCalledWith(game.player0, game.player1, new Point(0,0))
 )


ListView: forgotten lines of listview::removeItem refactoring

--- src/components/list/listviewcontroller.coffee
+++ src/components/list/listviewcontroller.coffee
@@ -70,11 +70,12 @@

     listView.on 'ItemWasRemoved', (view, index) =>

-      @unregisterItem itemInfo
+      @unregisterItem view, index
       @showNoItemWidget()  if noItemFoundWidget

     if options.keyNav
-      listView.on 'KeyDownOnList', (event)=> @keyDownPerformed listView, event
+      listView.on 'KeyDownOnList', (event) => @keyDownPerformed listView, event
+

   loadView: (mainView) ->

@@ -177,12 +178,11 @@

   unregisterItem: (itemInstance, index) ->

-    @emit "UnregisteringItem", {view, index}
-    {index, view} = itemInfo
-    actualIndex = if @getOptions().lastToFirst then @getListView().items.length - index - 1 else index
+    @emit 'UnregisteringItem', itemInstance, index
+    actualIndex = if @getOptions().lastToFirst then @getItemCount() - index - 1 else index
     @getListItems().splice actualIndex, 1
-    if view.getData()?
-      delete @itemsIndexed[view.getItemDataId()]
+    if itemInstance.getData()?
+      delete @itemsIndexed[itemInstance.getItemDataId()]

   replaceAllItems: (items) ->


runner: Use HTTP request to get input JSON

Going through HTML/DOM for the JSON was a bad idea,
and there never was a good reason for it in the first place.

--- src/runner.coffee
+++ src/runner.coffee
@@ -22,7 +22,7 @@
   .replace(/>/g, '>')
   .replace(/"/g, '"')

-generateHtml = (filter, page, options) ->
+generateHtml = (filter, options) ->

   library = """
   window.jsJobEvent = function(id, payload) {
@@ -45,6 +45,19 @@
     }
     return obj;
   };
+  var getData = function(callback) {
+    var xhr = new XMLHttpRequest();
+    xhr.open('GET', window.location.href+'/data', false);
+    xhr.setRequestHeader('Content-type', 'application/json; charset=utf-8');
+    xhr.onreadystatechange = function () {
+        if (xhr.readyState === 4) {
+            var json = xhr.responseText;
+            var data = JSON.parse(json);
+            return callback(null, data);
+        }
+    };
+    xhr.send();
+  };
   var sendResponse = function(err, solution, details) {
     var xhr = new XMLHttpRequest();
     xhr.open('POST', window.location.href, true);
@@ -63,19 +76,16 @@
   };
   var main = function() {
     console.log('poly: main start');
-    var dataElement = document.getElementById("poly-input-data");
-    var json = dataElement.innerHTML.substring("<!--".length, dataElement.innerHTML.length-"-->".length);
-    var data = JSON.parse(json);
-    console.log('poly: starting solving');
-    window.jsJobRun(data.page, data.options, cb);
-    console.log('poly: started');
+
+    getData(function(err, data) {
+      console.log('poly: starting solving');
+      window.jsJobRun(data.input, data.options, cb);
+      console.log('poly: started');
+    });
   };
   window.onload = main;
 //  main();
   """
-
-  payload = { page: page, options: options }
-  json = JSON.stringify payload, null, 4

   scriptTags = ("<script>#{s}</script>" for s in options.scripts).join("\n")
   body = """<!DOCTYPE html>
@@ -85,7 +95,6 @@
       #{scriptTags}
       <script>#{library}</script>
       <script src="#{filter}"></script>
-      <script id="poly-input-data" type="application/json"><!--#{json}--></script>
     </head>
     <body>
       <script>#{script}</script>
@@ -186,10 +195,24 @@
       jobId = paths[2]
       if paths[3] == 'event'
         return @handleEventRequest jobId, request, response
+      else if paths[3] == 'data'
+        return @handleDataRequest jobId, request, response
       else
         return response.end()
     else
       return response.end()
+
+  handleDataRequest: (jobId, request, response) ->
+    console.log "#{request.method} #{jobId}" if @options.verbose
+    job = @jobs[jobId]
+    if not job
+      # multiple callbacks for same id, or wrong id
+      debug 'could not find solve job', jobId
+      return
+
+    response.writeHead 200, {"Content-Type": "application/json; charset=utf-8"}
+    body = JSON.stringify { input: job.page, options: job.options }
+    response.end body

   handleSolveRequest: (jobId, request, response) ->
     console.log "#{request.method} #{jobId}" if @options.verbose
@@ -200,9 +223,8 @@
       return

     if request.method == 'GET'
-      # FIXME: make only for GET
       response.writeHead 200, {"Content-Type": "text/html; charset=utf-8"}
-      body = generateHtml job.filter, job.page, job.options
+      body = generateHtml job.filter, job.options
       response.end body
     else if request.method == 'POST'
       data = ""