async overhaul of queries/_runner

async overhaul of queries/_runner

--- queries/_runner.coffee
+++ queries/_runner.coffee
@@ -1,6 +1,8 @@
 _       = require "underscore"
 fs      = require "fs"
+async   = require "async"
 mkdirp  = require "mkdirp"
+db      = require "../lib/db"

 majors = [
   {
@@ -8,14 +10,14 @@
     values: ["all"]
   }
   {
+    name: "player"
+    sql: "GET_PLAYER_NAME(p.playerid,'','') = '?'"
+    values: ["Dan", "Tom", "JD", "Jeran", "Guest", "Spirk"]
+  }
+  {
     name: "year"
     sql: "YEAR(t.matchdate) = ?"
     values: [2004,2007,2008,2009,2010,2011,2012]
-  }
-  {
-    name: "player"
-    sql: "GET_PLAYER_NAME(p.playerid,'','') = '?'"
-    values: ["Dan", "Tom", "JD", "Jeran", "Guest", "Spirk"]
   }
   {
     name: "location"
@@ -66,52 +68,108 @@
 outfile_path_root = "#{__dirname}/../parts/tables/"
 list_item_limit = 10

-for major in majors
+
+sql = {}
+fn_vals = {}
+
+major_fn = (major, cb) ->
+  console.log "major: ", major
+  fn_vals.major = major
   sql = {}
-  for major_value in major.values
-    if major.sql
-      sql.major = major.sql.replace "?", major_value
-    else
-      delete sql.major
-
-    for minor in minors
-
-      for minor_value in minor.values
-        if minor.sql
-          sql.minor = minor.sql.replace "?", minor_value
-        else
-          delete sql.major
-
-        for script in scripts
-          script_filename = "#{__dirname}/individual/#{script}"
-
-          for game_type in game_types.values
-            do (game_type) ->
-              caption = "#{game_type}-player Games"
-
-              sql.game_type = game_types.sql.replace "?", game_type
-
-              sql_pieces = [sql.major, sql.minor, sql.game_type]
-              sql_pieces = _.compact sql_pieces
-
-              sql_string = sql_pieces.join " AND "
-
-              path_pieces = [major.name, major_value, minor.name, minor_value, script, game_type+"p"]
-              path = outfile_path_root + path_pieces.join "/"
-
-              opts =
-                caption: caption
-                where_sql_snippet: sql_string
-                outfile_path: path
-                limit: list_item_limit
-
-              exists = fs.existsSync path
-
-              if exists
-                require(script_filename)(opts)
-              else
-                mkdirp path, (err) -> require(script_filename)(opts)
-
-
-
-                  +  async.forEachSeries major.values, major_value_fn, (err) ->
+    console.log "---------------------------------"
+    cb err
+
+major_value_fn = (major_value, cb) ->
+  console.log "major_value: ",
+  fn_vals.major_value = major_value
+  if fn_vals.major.sql
+    sql.major = fn_vals.major.sql.replace "?", major_value
+  else
+    delete sql.major
+
+  async.forEachSeries minors, minor_fn, cb
+
+minor_fn = (minor, cb) ->
+  console.log "minor: ", minor
+  fn_vals.minor = minor
+  async.forEachSeries minor.values, minor_value_fn, cb
+
+minor_value_fn = (minor_value, cb) ->
+  console.log "minor_value: ", minor_value
+  fn_vals.minor_value = minor_value
+  if fn_vals.minor.sql
+    sql.minor = fn_vals.minor.sql.replace "?", minor_value
+  else
+    delete sql.minor
+
+  console.log "scripts: ", scripts
+
+  async.forEachSeries scripts, script_fn, cb
+
+script_fn = (script, cb) ->
+  fn_vals.script_filename = "#{__dirname}/individual/#{script}"
+
+  async.forEachSeries game_types.values, game_type_fn, cb
+
+game_type_fn = (game_type, cb) ->
+  fn_vals.game_type = game_type
+  sql.game_type = game_types.sql.replace "?", game_type
+
+  sql_string = build_where_snippet sql
+
+  path = build_path fn_vals
+
+  caption = build_caption fn_vals
+
+  opts =
+    caption: caption
+    where_sql_snippet: sql_string
+    outfile_path: path
+    limit: list_item_limit
+
+  query_fn = require(fn_vals.script_filename)
+
+  console.log "opts before running: ", opts
+
+  if fs.existsSync path
+    query_fn opts, cb
+  else
+    mkdirp path, (err) ->
+      query_fn opts, cb
+
+build_where_snippet = (opts) ->
+  sql_pieces = [opts.major, opts.minor, opts.game_type]
+  sql_pieces = _.compact sql_pieces
+
+  sql_string = sql_pieces.join " AND "
+  return sql_string
+
+build_path = (opts) ->
+  path_pieces = [
+    opts.major.name
+    opts.major_value
+    opts.minor.name
+    opts.minor_value
+    opts.script
+    opts.game_type+"p"
+  ]
+
+  path = outfile_path_root + path_pieces.join "/"
+  return path
+
+build_caption = (opts) ->
+  caption_pieces = []
+  unless opts.major.name is "all"
+    caption_pieces.push "#{opts.major.name}: #{opts.major_value}"
+  unless opts.minor.name is "all"
+    caption_pieces.push "#{opts.minor.name}: #{opts.minor_value}"
+
+  caption_pieces.push "#{opts.game_type}P Games"
+
+  caption = caption_pieces.join ", "
+  return caption
+
+async.forEachSeries majors, major_fn, (major_err) ->
+  console.log "major callback, closing db"
+  db.closeConn()


add office ip to allowed IPs

add office ip to allowed IPs


Remove tooltips from beatmap panels

Remove tooltips from beatmap panels

--- resources/assets/coffee/react/beatmaps/panel.coffee
+++ resources/assets/coffee/react/beatmaps/panel.coffee
@@ -38,19 +38,19 @@
         div className: 'thumb_cover', style: {backgroundImage: "url(//b.ppy.sh/thumb/#{beatmap.beatmapset_id}l.jpg)"}
         div className: 'bottom_left',
           div className: 'title',
-            span title: beatmap.title, beatmap.title
+            span {}, beatmap.title
           div className: 'artist',
-            span title: beatmap.artist, beatmap.artist
+            span {}, beatmap.artist

         div className: 'top_right',
           div className: 'stats',
             div className: 'plays',
-              span title: beatmap.play_count,
+              span {},
                 beatmap.play_count
               i className: 'fa fa-play-circle'

             div className: 'favourites',
-              span title: beatmap.favourite_count,
+              span {},
                 beatmap.favourite_count
               i className: 'fa fa-heart'


Update ProjectInvite model

Update ProjectInvite model

--- services/web/app/coffee/models/ProjectInvite.coffee
+++ services/web/app/coffee/models/ProjectInvite.coffee
@@ -4,15 +4,21 @@
 Schema = mongoose.Schema
 ObjectId = Schema.ObjectId

+THIRTY_DAYS_IN_SECONDS = 60 * 60 * 24 * 30
+
+makeExpirationDate = () ->
+	nowInMillis = Date.now()
+	new Date(nowInMillis + (1000 * THIRTY_DAYS_IN_SECONDS))
+
+
 ProjectInviteSchema = new Schema
-	project_id:     ObjectId
-	from_user_id:   ObjectId
-	privilegeLevel: String
-	# For existing users
-	to_user_id:     ObjectId
-	# For non-existant users
-	hashed_token:   String
 	email:          String
+	token:          String
+	sendingUserId:  ObjectId
+	projectId:      ObjectId
+	privileges:     String
+	createdAt:      {type: Date, default: Date.now}
+	expiresAt:      {type: Date, default: makeExpirationDate}

 conn = mongoose.createConnection(Settings.mongo.url, server: poolSize: Settings.mongo.poolSize || 10)

@@ -20,4 +26,4 @@

 mongoose.model 'ProjectInvite', ProjectInviteSchema
 exports.ProjectInvite = ProjectInvite
-exports.ProjectInviteSchema = ProjectInviteSchema+exports.ProjectInviteSchema = ProjectInviteSchema


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/animation.coffee
+++ test/animation.coffee
@@ -1,6 +1,7 @@
+###
 test "Animation should default to first frame", ->
   animation = Animation.fromPixieId 45

   equals animation.active(), 0
-
+###


collision with regular bounds

collision with regular bounds

--- test/collision.coffee
+++ test/collision.coffee
@@ -55,6 +55,7 @@
     height: 10

   start = Point(0, 0)
+
   Collision.rayRectangle
 module()


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/expirable.coffee
+++ test/expirable.coffee
@@ -21,6 +21,8 @@
     duration: 10
     alpha: 0.8

+  obj.include Expirable

+  5.times

 module()


Add more tests for EDDSA, also with malformed sigs


fixed

fixed


adding dev statement to dev logs

adding dev statement to dev logs

--- server/lib/log.coffee
+++ server/lib/log.coffee
@@ -213,7 +213,7 @@

       if loglevels.indexOf(loglevel) <= loglevels.indexOf 'debug'
         console.log opts.msg.toUpperCase() if opts.msg
-        console.log JSON.stringify(opts), '\n'
+        console.log (if API.settings.dev then 'DEV\n' else '') + JSON.stringify(opts), '\n'

   catch err
     console.log 'API LOG ERROR\n', opts, '\n', fn, '\n', lvl, '\n', err


Remove blanks from empty lines.

Remove blanks from empty lines.

--- test/unit/main.coffee
+++ test/unit/main.coffee
@@ -37,12 +37,12 @@
 requirejs.config
   # The application Javascript URL.
   baseUrl: rootUrl + '/_public/javascripts'
-
+
   # Undocumented karama-requirejs magic added per
   # https://github.com/karma-runner/karma-requirejs/issues/6.
   # client:
   #   requireJsShowNoTimestampsError: false
-
+
   # The test module paths replicates the web app main config paths, but
   # points only to the locally installed libraries and includes the test
   # specs.


'handle' file writestream error

'handle' file writestream error

--- lib/atom-io-client.coffee
+++ lib/atom-io-client.coffee
@@ -124,7 +124,7 @@
     imagePath = @avatarPath login
     stream = fs.createWriteStream imagePath
     stream.on 'finish', () -> callback(null, imagePath)
-    # TODO stream.on error
+    stream.on 'error', (error) -> callback(error)
     request("https://github.com/#{login}.png").pipe(stream)

   # The cache expiry doesn't need to be clever, or even compare dates, it just


Fix up cache storing and expiry check

Fix up cache storing and expiry check

--- lib/atom-io-client.coffee
+++ lib/atom-io-client.coffee
@@ -35,7 +35,7 @@
           cached =
             data: data
             createdOn: Date.now()
-          localStorage.setItem(packagePath, JSON.stringify(data))
+          localStorage.setItem(packagePath, JSON.stringify(cached))
           callback(err, cached.data) # TODO handle parse error

   fetchFromCache: (packagePath, options, callback) ->
@@ -44,7 +44,7 @@
     cached = localStorage.getItem(packagePath)
     cached = if cached then JSON.parse(cached)
     # TODO - Needs to always return cached data when api is unreachable
-    if cached and (options.force or (Date.now() - cached.createdOn > @expiry))
+    if cached? and (options.force or (Date.now() - cached.createdOn < @expiry))
       cached ?=  {data: {}}
       callback(null, cached.data)
     else


remove console logs

remove console logs

--- lib/atom-lupa-view.coffee
+++ lib/atom-lupa-view.coffee
@@ -43,13 +43,10 @@
     @message.innerHTML = content

   setFiles: (files, addLabelDecoration)->
-    console.log("AAAD", files.length, addLabelDecoration)
+
     onChange = (line) ->
         addLabelDecoration(preview, line + 1, 0, line + 2, 0, [], 'label-decoration')
     preview = @preview
-    console.log("DBG T", preview)
-
-

     totalLoc = files.reduce (res, f) ->


Don't break when there are not selected items.

--- lib/birch-markdown.coffee
+++ lib/birch-markdown.coffee
@@ -27,7 +27,7 @@
   setItemType: (type) ->
     editor = @birchOutlineEditorService?.getActiveOutlineEditor()
     items = editor?.selection.items
-    if editor and items.length > 0
+    if editor and items?.length > 0
       outline = editor.outline
       outline.beginUpdates()
       for each in items
@@ -37,4 +37,5 @@
   copyAsMarkdown: () ->
     outline = @birchOutlineEditorService?.getActiveOutlineEditor()?.outline
     if outline
-      atom.clipboard.write BirchToMarkdown.outlineToMarkdown outline
+      markdown = BirchToMarkdown.outlineToMarkdown outline
+      atom.clipboard.write markdown


track initialization problems to photo load timing

--- views/puzzle.coffee
+++ views/puzzle.coffee
@@ -117,10 +117,18 @@
     console.log "   @unset_pieces = #{up}"
     @pieces_in_puzzle = 16
     @pieces_set = 0
-    @puzzle.mask.reset_mask(@puzzle.grid)
+    #  ### N E W  <3> ###  #
+    # @puzzle.mask.draw_mask(@puzzle.grid)
+    # @puzzle.mask.reset_mask(@puzzle.grid)
+    console.log "   ** snp 1 **"
     @puzzle.ui_status.reset()
     @puzzle.puzzle_view.reset()
+    console.log "   ** snp 2 **"
+    #  ### N E W  <4> ###  #
+    @puzzle.mask.draw_mask(@puzzle.grid)
+    console.log "   ** snp 3 **"
     @start_first_piece()
+    console.log "   ** snp 4 **"

   set_piece: () ->
@@ -166,6 +174,11 @@
   start_first_piece: () ->
     @puzzle.colors.new_rotation()
     @puzzle.indicator.start_indicator()
+    #  ### N E W  <1> (didn't work) ###  #
+    @puzzle.mask.reset_mask(@puzzle.grid)
+    #  ### N E W  <2> ) ###  #
+    #  ### N E W  <3> (move this to start_new_puzzle() ) ###  #
+    # @puzzle.mask.draw_mask(@puzzle.grid)
     pc = Math.floor(@pieces_in_puzzle*Math.random())
     @sym = @unset_pieces[pc]
     # @unset_pieces.splice(@unset_pieces.indexOf(@sym),1)
@@ -201,10 +214,23 @@

   draw_mask: (grid) ->
-    @hex_draw.set_context("canvas")
+    console.log " "
+    console.log "draw_mask()"
+    console.log "   & & & & @color = #{@color} & & & &"
+    #  ### N E W  <6> ###  #
+    # @hex_draw.set_context("canvas")
+    # cnv = document.getElementById("puzzle-widget")
+    # ctx = cnv.getContext('2d')
+    zzz = 0 # EXTRA
     for bb in [1..10]
       for aa in [1..24]
-        @hex_draw.fill_hex_ab(aa,bb,@color) if grid[bb][aa] in @puzzle.pz_status.unset_pieces
+        # @hex_draw.fill_hex_ab(aa,bb,@color) if grid[bb][aa] in @puzzle.pz_status.unset_pieces
+        if grid[bb][aa] in @puzzle.pz_status.unset_pieces
+          #  ### N E W  <7> ###  #
+          @hex_draw.ctx_fill_hex_ab(aa,bb,@color)
+          # @hex_draw.fill_hex_ab(aa,bb,@color)
+          zzz = zzz + 1
+    console.log "   #{zzz} hexes added to mask"

@@ -379,6 +405,7 @@
     @reset()
     @photo = new Image()
     @photo.onload = =>
+      console.log ">>>>>>>>>>>>> @photo.onload EVENT <<<<<<<<<<<<<"
       @draw_photo()
     @photo.src = '/pz-photo'

@@ -391,6 +418,7 @@

   draw_photo: () ->
+    console.log "   * * * draw_photo()"
     @context = @get_drawing_context("canvas")
     @context.drawImage(@photo,@puzzle_xy[0],@puzzle_xy[1])

@@ -467,6 +495,14 @@

   fill_hex_ab: (a,b,color) ->
+    xy = @get_hex_xy(a,b)
+    @fill_hex_xy(xy[0],xy[1],color)
+
+
+  ctx_fill_hex_ab: (ctx,a,b,color) ->
+    #  ### <9> ###  #
+    cnv = document.getElementById("puzzle-widget")
+    @context = cnv.getContext('2d')
     xy = @get_hex_xy(a,b)
     @fill_hex_xy(xy[0],xy[1],color)


Add test for foldable icons on soft wrapped lines

--- spec/text-editor-component-spec.coffee
+++ spec/text-editor-component-spec.coffee
@@ -636,6 +636,18 @@
           editor.unfoldBufferRow(5)
           nextAnimationFrame()
           expect(lineNumberHasClass(5, 'folded')).toBe false
+
+        describe "when soft wrapping is enabled", ->
+          beforeEach ->
+            editor.setSoftWrapped(true)
+            nextAnimationFrame()
+            componentNode.style.width = 16 * charWidth + editor.getVerticalScrollbarWidth() + 'px'
+            component.measureHeightAndWidth()
+            nextAnimationFrame()
+
+          it "doesn't add the foldable class for soft-wrapped lines", ->
+            expect(lineNumberHasClass(0, 'foldable')).toBe true
+            expect(lineNumberHasClass(1, 'foldable')).toBe false

       describe "mouse interactions with fold indicators", ->
         [gutterNode] = []


Disable stream when output to clipboard.

--- lib/process-config.coffee
+++ lib/process-config.coffee
@@ -27,6 +27,10 @@
     if @outputTarget not in ["panel", "editor", "file", "clipboard", "console", "void"]
       @outputTarget = "void";

+    # Do not allow streaming to the clipboard.
+    if @outputTarget == "clipboard"
+      @stream = false;
+
     if !@arguments
       @arguments = [];


bindCheckboxFields -> bindInputFields

bindCheckboxFields -> bindInputFields

--- lib/settings-panel.coffee
+++ lib/settings-panel.coffee
@@ -33,7 +33,7 @@

     @appendSettings(namespace, settings)

-    @bindCheckboxFields()
+    @bindInputFields()
     @bindSelectFields()
     @bindEditors()

@@ -64,7 +64,7 @@
   sortSettings: (namespace, settings) ->
     _.chain(settings).keys().sortBy((name) -> name).sortBy((name) -> atom.config.getSchema("#{namespace}.#{name}")?.order).value()

-  bindCheckboxFields: ->
+  bindInputFields: ->
     @find('input[id]').toArray().forEach (input) =>
       input = $(input)
       name = input.attr('id')


Re-added download overlay

Re-added download overlay

--- static/js/dss/lib/peneloPlay.coffee
+++ static/js/dss/lib/peneloPlay.coffee
@@ -128,7 +128,7 @@

             #Might not need the progress overlay if firefucks get their act together
             whileloading: =>
-                if not soundManager.html5.mp3
+                if true #not soundManager.html5.mp3
                     percentageFinished = (_player.bytesLoaded / _player.bytesTotal) * 100
                     percentageWidth = (bounds.waveformWidth / 100) * percentageFinished
                     ui.downloadOverlay.css "width", percentageWidth


If TEST_JUNIT_XML_PATH is set, output JUnit XML format test results

--- spec/jasmine-test-runner.coffee
+++ spec/jasmine-test-runner.coffee
@@ -6,6 +6,10 @@
 module.exports = ({logFile, headless, testPaths, buildAtomEnvironment}) ->
   window[key] = value for key, value of require '../vendor/jasmine'
   require 'jasmine-tagged'
+
+  if process.env.TEST_JUNIT_XML_PATH
+    require 'jasmine-reporters'
+    jasmine.getEnv().addReporter new jasmine.JUnitXmlReporter(process.env.TEST_JUNIT_XML_PATH, true, true)

   # Allow document.title to be assigned in specs without screwing up spec window title
   documentTitle = null


Add notification value

Add notification value

--- spec/language-babel-spec.coffee
+++ spec/language-babel-spec.coffee
@@ -202,7 +202,7 @@
         lb.transpile(path.resolve(__dirname, 'fixtures/dira/dira.1/dira.2/react.jsx'))
         #may take a while for the transpiler to run and call home
         waitsFor ->
-          notificationSpy.callCount
+          notificationSpy.callCount > 1
         runs ->
           expect(notificationSpy.callCount).to.equal(2)
           msg = notificationSpy.calls[0].args[0].message


make menus actually useful

make menus actually useful

--- menus/atom-haxe.cson
+++ menus/atom-haxe.cson
@@ -11,8 +11,21 @@
       'label': 'Haxe'
       'submenu': [
         {
-          'label': 'Toggle'
-          'command': 'haxe:toggle'
+          'label': 'Start/Reset completion server'
+          'command': 'haxe:reset-server'
+        },
+        {
+          'label': 'Stop completion server'
+          'command': 'haxe:stop-server'
+        },
+        { type: 'separator' },
+        {
+          'label': 'Toggle completion debug'
+          'command': 'haxe:toggle-completion-debug'
+        },
+        {
+          'label': 'Toggle log view'
+          'command': 'haxe:toggle-log-view'
         }
       ]
     ]


屏蔽应用配置

屏蔽应用配置

--- menus/chameleon.cson
+++ menus/chameleon.cson
@@ -16,10 +16,10 @@
       'label':'模块配置'
       'command':'chameleon:configure-module'
     }
-    {
-      'label':'应用配置'
-      'command':'chameleon:configure-application'
-    }
+    # {
+    #   'label':'应用配置'
+    #   'command':'chameleon:configure-application'
+    # }
     {
       'label':'构建应用'
       'command':'chameleon:bulid-project'


Test for adding a node via search

Test for adding a node via search

--- spec/EditGraph.coffee
+++ spec/EditGraph.coffee
@@ -1,10 +1,22 @@
-describe 'Graph Editor', ->
+describe 'Editing a graph', ->
   win = null
   doc = null
+  editor = null
+  graph = null
   before ->
     iframe = document.getElementById 'app'
     win = iframe.contentWindow
     doc = iframe.contentDocument
+
+  describe 'initially', ->
+    it 'should have a graph editor available', ->
+      editor = doc.querySelector 'the-graph-editor'
+      chai.expect(editor).to.be.an 'object'
+      graph = editor.shadowRoot.querySelector 'the-graph'
+      chai.expect(graph).to.be.an 'object'
+    it 'should have no nodes in the graph editor', ->
+      nodes = graph.shadowRoot.querySelectorAll 'g.nodes g.node'
+      chai.expect(nodes.length).to.equal 0

   describe 'runtime', ->
     runtime = null
@@ -51,3 +63,16 @@
         chai.expect(search.results.length).to.equal 1
         done()
       , 1000
+    it 'should add a node when result is clicked', (done) ->
+      context = doc.querySelector 'noflo-context'
+      chai.expect(context).to.be.an 'object'
+      results = context.shadowRoot.querySelector 'noflo-search-results'
+      chai.expect(results).to.be.an 'object'
+      getelement = results.shadowRoot.querySelector 'li'
+      chai.expect(getelement).to.be.an 'object'
+      Syn.click getelement
+      setTimeout ->
+        nodes = graph.shadowRoot.querySelectorAll 'g.nodes g.node'
+        chai.expect(nodes.length).to.equal 1
+        done()
+      , 1000


Add tests for faces on node target

Add tests for faces on node target

--- spec/FindFaces.coffee
+++ spec/FindFaces.coffee
@@ -83,3 +83,118 @@
         chai.expect(results[1].confidence).to.be.at.least results[2].confidence
         chai.expect(results[2].confidence).to.be.at.least results[3].confidence
         chai.expect(results[3].confidence).to.be.at.least results[4].confidence
+
+      unless noflo.isBrowser()
+        it 'should find the right faces', ->
+          expected = [
+            x: 492,
+            y: 268,
+            width: 57,
+            height: 57,
+            neighbors: 7,
+            confidence: 11.28613306999998
+          ,
+            x: 367,
+            y: 271,
+            width: 56,
+            height: 56,
+            neighbors: 8,
+            confidence: 11.188660410000004
+          ,
+            x: 172,
+            y: 295,
+            width: 57,
+            height: 57,
+            neighbors: 5,
+            confidence: 8.33351699000001
+          ,
+            x: 993,
+            y: 508,
+            width: 62,
+            height: 62,
+            neighbors: 5,
+            confidence: 5.652669490000009
+          ,
+            x: 296,
+            y: 477,
+            width: 65,
+            height: 65,
+            neighbors: 8,
+            confidence: 5.49484601000000
+          ,
+            x: 485,
+            y: 353,
+            width: 55,
+            height: 55,
+            neighbors: 6,
+            confidence: 4.98707517000000
+          ,
+            x: 646,
+            y: 254,
+            width: 58,
+            height: 58,
+            neighbors: 2,
+            confidence: 2.481149969999999
+          ,
+            x: 590,
+            y: 441,
+            width: 69,
+            height: 69,
+            neighbors: 3,
+            confidence: 2.256392189999995
+          ,
+            x: 461,
+            y: 502,
+            width: 61,
+            height: 61,
+            neighbors: 6,
+            confidence: 1.986859089999997
+          ,
+            x: 1222,
+            y: 378,
+            width: 72,
+            height: 72,
+            neighbors: 5,
+            confidence: 1.325425789999999
+          ,
+            x: 366,
+            y: 383,
+            width: 61,
+            height: 61,
+            neighbors: 4,
+            confidence: 1.290298050000000
+          ,
+            x: 861,
+            y: 282,
+            width: 59,
+            height: 59,
+            neighbors: 8,
+            confidence: 1.289433169999998
+          ,
+            x: 962,
+            y: 312,
+            width: 53,
+            height: 53,
+            neighbors: 4,
+            confidence: -0.1545753700000000
+          ,
+            x: 1049,
+            y: 394,
+            width: 61,
+            height: 61,
+            neighbors: 1,
+            confidence: -0.4406286100000015
+          ,
+            x: 1050,
+            y: 295,
+            width: 58,
+            height: 58,
+            neighbors: 2,
+            confidence: -2.9389983699999966
+          ]
+          for face,i in results
+            delta = 1
+            chai.expect(face.x).to.be.closeTo expected[i].x, delta
+            chai.expect(face.y).to.be.closeTo expected[i].y, delta
+            chai.expect(face.width).to.be.closeTo expected[i].width, delta
+            chai.expect(face.height).to.be.closeTo expected[i].height, delta


VFL uses just 'horizontal' and 'vertical' now

--- spec/preparser.coffee
+++ spec/preparser.coffee
@@ -54,7 +54,7 @@

   describe 'with a simple VFL rule', ->
     source = """
-    @-gss-horizontal |-[#box1]-[#button1]-| in(#dialog);
+    @horizontal |-[#box1]-[#button1]-| in(#dialog);
     """
     statements = null
     it 'should produce a statement array', ->
@@ -89,7 +89,7 @@
     source = """
     /* Here we define some constraints */
     #box1.width >= #box2.width;
-    @-gss-horizontal |-[#box1]-[#button1]-| in(#dialog);
+    @horizontal |-[#box1]-[#button1]-| in(#dialog);
     /* And then we lay it all out */
     @-gss-layout "frontpageLayout" {
       grid: "aaab"
@@ -132,7 +132,7 @@
     }
     /* Here we define some constraints */
     #box1.width >= #box2.width;
-    @-gss-horizontal |-[#box1]-[#button1]-| in(#dialog);
+    @horizontal |-[#box1]-[#button1]-| in(#dialog);
     /* And then we lay it all out */
     @-gss-layout "frontpageLayout" {
       grid: "aaab"


Rename Project.buildEditSession -> Project.open

--- spec/tabs-spec.coffee
+++ spec/tabs-spec.coffee
@@ -36,7 +36,7 @@
     registerDeserializer(TestView)
     item1 = new TestView('Item 1')
     item2 = new TestView('Item 2')
-    editSession1 = project.buildEditSession('sample.js')
+    editSession1 = project.open('sample.js')
     paneContainer = new PaneContainer
     pane = new Pane(item1, editSession1, item2)
     pane.showItem(item2)
@@ -77,7 +77,7 @@
       expect(tabBar.tabAtIndex(1).find('.title')).toHaveText 'Item 3'

     it "adds the 'modified' class to the new tab if the item is initially modified", ->
-      editSession2 = project.buildEditSession('sample.txt')
+      editSession2 = project.open('sample.txt')
       editSession2.insertText('x')
       pane.showItem(editSession2)
       expect(tabBar.tabForItem(editSession2)).toHaveClass 'modified'


Remove unnecessary trigger

Remove unnecessary trigger

--- elements/js/shots.coffee
+++ elements/js/shots.coffee
@@ -79,7 +79,6 @@
       @add @newGroup(shot)
     else
       @last().get('shots').add(shot)
-    @trigger 'update'

   newGroup: (shot) ->
     shots = new Shots([shot])


Update Paypal.coffee

Update Paypal.coffee

resolves #1

--- lib/Paypal.coffee
+++ lib/Paypal.coffee
@@ -30,7 +30,7 @@

     if env is "production"
       @endpointUrl = "https://api-3t.paypal.com/nvp"
-      @checkoutUrl = "https://www.paypal.com/cgi?bin/webscr?cmd=_express-checkout&token="
+      @checkoutUrl = "https://www.paypal.com/cgi-bin/webscr?cmd=_express-checkout&token="
     else
       @endpointUrl = "https://api-3t.sandbox.paypal.com/nvp"
       @checkoutUrl = "https://www.sandbox.paypal.com/webscr?cmd=_express-checkout&token="
@@ -282,4 +282,4 @@

     date_utc.toISOString()

-module.exports = Paypal+module.exports = Paypal


remove comment (fixed)

remove comment (fixed)

--- lib/client.coffee
+++ lib/client.coffee
@@ -10,7 +10,6 @@
     @cmdParser = new XmlParser()
     @cmdParser.on 'command', (command) => @handleCmd(command)

-  # TODO: error handling on cmd or socket errors
   connect: ->
     @socket = new net.Socket()
     @socket.connect @port, @host, =>


fix test failure from merge

fix test failure from merge

--- services/web/test/unit/coffee/TokenAccess/TokenAccessControllerTests.coffee
+++ services/web/test/unit/coffee/TokenAccess/TokenAccessControllerTests.coffee
@@ -407,7 +407,7 @@
 				@res.redirect = sinon.stub()
 				@next = sinon.stub()
 				@TokenAccessHandler.findProjectWithReadOnlyToken = sinon.stub()
-						.callsArgWith(1, null, @project)
+						.callsArgWith(1, null, @project, true)
 				@TokenAccessHandler.checkV1Access = sinon.stub().callsArgWith(1, null, false, 'doc-url')
 				@TokenAccessController.readOnlyToken @req, @res, @next


Debug draw: Fix incorrect culling. Fix capsules. Add wide line.

--- engine/debug_draw.coffee
+++ engine/debug_draw.coffee
@@ -227,6 +227,7 @@
         # TODO: draw something else when it's too small (a different arrow?)
         #       and a circle when it's 0
         dob = shapes.arrow
+        dob.radius = vec3.len vector
         color4.copy shapes.color, color
         vec2.set shapes.discontinuity, 1, 1
         vec3.copy dob.position, position
@@ -253,12 +254,14 @@
             @segment_count=10
             @segment_ratio=.75
             @ttl_frames=0
+            @use_wide=false
         } = options

     _draw: (shapes, render_manager, camera_position) ->
         {positions: [p1, p2], color, segment_count, segment_ratio} = this
         # TODO: draw a circle when it's 0
-        dob = shapes.line
+        dob = if @use_wide then shapes.wide_line else shapes.line
+        dob.radius = vec3.dist p1, p2
         color4.copy shapes.color, color
         vec2.set shapes.discontinuity, segment_count, segment_ratio
         vec3.copy dob.position, p1
@@ -283,6 +286,7 @@
     _draw: (shapes, render_manager, camera_position) ->
         {position, color, size} = this
         dob = shapes.cross3d
+        dob.radius = size
         color4.copy shapes.color, color
         vec3.set dob.scale, size, size, size
         vec3.copy dob.position, position
@@ -406,6 +410,16 @@
         @line = new @context.Mesh
         d = [0,0,0,  0,0,1]
         @line.load_from_lists d, [0,1]
+
+        @wide_line = new @context.Mesh
+        d = [
+            0,0,0,  0,0,1,
+            1,0,0,  1,0,1,
+            0,1,0,  0,1,1,
+            -1,0,0,  -1,0,1,
+            0,-1,0,  0,-1,1,
+        ]
+        @wide_line.load_from_lists d, [0,1,2,3,4,5,6,7,8,9]

         @cross3d = new @context.Mesh
         d = [-1,0,0,  1,0,0, 0,-1,0,  0,1,0, 0,0,-1,  0,0,1, ]
@@ -457,6 +471,43 @@
                 {varname: 'discontinuity', value: @discontinuity}
             ]
         }
+        @px_size = vec2.create()
+        @material_wide_line = new @context.Material '_debug_wl', {
+            material_type: 'PLAIN_SHADER'
+            vertex: """
+                precision highp float;
+                precision highp int;
+                uniform mat4 model_view_matrix;
+                uniform mat4 projection_matrix;
+                attribute vec3 vertex;
+                uniform vec2 discontinuity;
+                uniform vec2 px_size;
+                varying vec2 vardiscont;
+                void main()
+                {
+                    vec4 pos = projection_matrix * model_view_matrix * vec4(0., 0., vertex.z, 1.0);
+                    vardiscont = vec2(
+                        vertex.z * discontinuity.x - vertex.z * (1.-discontinuity.y),
+                        discontinuity.y
+                    );
+                    pos.xy += vertex.xy * px_size;
+                    pos.z -= 0.0005;
+                    gl_Position = pos;
+                }"""
+            fragment: """
+                precision mediump float;
+                uniform vec4 color;
+                varying vec2 vardiscont;
+                void main(){
+                    if(vardiscont.x-floor(vardiscont.x) > vardiscont.y) discard;
+                    gl_FragColor = color;
+                }"""
+            uniforms: [
+                {varname: 'color', value: @color}
+                {varname: 'discontinuity', value: @discontinuity}
+                {varname: 'px_size', value: @px_size}
+            ]
+        }

         for k,ob of this when ob.type == 'MESH'
             ob.elements = []
@@ -465,6 +516,7 @@
             ob.data.draw_method = @context.render_manager.gl.LINES
             ob.rotation_order = 'Q'
             ob._update_matrices()
+        @wide_line.materials = [@material_wide_line]

         @grids = {}
         @capsules = {}
@@ -494,6 +546,7 @@
             ob.materials = [@material]
             ob.data.draw_method = @context.render_manager.gl.LINES
             ob.rotation_order = 'Q'
+            ob.radius = cell_size * divisions * 0.5 * 1.5
             ob._update_matrices()
         return ob

@@ -501,7 +554,7 @@
     get_capsule: (params) ->
         key = JSON.stringify params
         ob = @capsules[key]
-        if not grid?
+        if not ob?
             {sin, cos} = Math
             ob = @capsules[key] = new @context.Mesh
             {radius, height} = params
@@ -536,6 +589,7 @@
             ob.data.draw_method = @context.render_manager.gl.LINES
             ob.rotation_order = 'Q'
             ob._update_matrices()
+            ob.radius = height * 0.5
         return ob

     # @nodoc


Fix bug in new loose end reporting

Fix bug in new loose end reporting

--- lib/doctor.coffee
+++ lib/doctor.coffee
@@ -70,7 +70,7 @@
       @scan next
     unless nexts.length
       unless @state[node] == 'done'
-        @add_loose_end node, stack[0..-1]
+        @add_loose_end node, @stack[0..-1]
     @stack.pop()

   add_loose_end: (node, stack) ->


Proper error stack order

Proper error stack order

--- lib/errors.coffee
+++ lib/errors.coffee
@@ -7,7 +7,7 @@
     trace = @stack
     { key, manager } = @worker
     { slice } = manager
-    @tail.unshift { trace, key, slice }
+    @tail.push { trace, key, slice }

   get_tail: -> @tail


Bug fix in memberOfGroup

Bug fix in memberOfGroup

--- lib/groups.coffee
+++ lib/groups.coffee
@@ -61,7 +61,7 @@
   when role in roles

 @memberOfGroup = (group, user = Meteor.user()) ->
-  escaped = escapeGroup group.name ? group
+  escaped = escapeGroup (group?.name ? group)
   not _.isEmpty(user?.roles?[escaped]) or
   not _.isEmpty(user?.rolesPartial?[escaped])


:new: Add Linter::getEditorLinterByPath

:new: Add Linter::getEditorLinterByPath

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

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


Fixed router.coffee

Fixed router.coffee

--- lib/router.coffee
+++ lib/router.coffee
@@ -32,11 +32,11 @@

       message: 'Projects Page',
       projects: projects
-      @route 'inboxLanding',
+  @route 'inboxLanding',
    	 path: '/inbox'

   @route 'notificationsLanding',
     path: '/notifications'
-      message:'Notifications Page',
-      notifications:()->
-        Notifications.find()+    message:'Notifications Page',
+    notifications:()->
+      Notifications.find()


Fixed issues with Runner

Fixed issues with Runner

--- lib/runner.coffee
+++ lib/runner.coffee
@@ -14,13 +14,14 @@
   run: (command, extraArgs, codeContext) ->
     @startTime = new Date()

+    args = @args(codeContext, extraArgs)
+    options = @options()
+    stdout = @stdoutFunc
+    stderr = @stderrFunc
+    exit = @onExit
+
     @bufferedProcess = new BufferedProcess({
-      command,
-      @args(codeContext, extraArgs),
-      @options(),
-      @stdoutFunc,
-      @stderrFunc,
-      @onExit
+      command, args, options, stdout, stderr, exit
     })

     if @inputProvider
@@ -31,13 +32,13 @@
   stdoutFunc: (output) =>
     @emitter.emit 'did-write-to-stdout', { message: output }

-  onDidWriteToStdout: (callback) =>
+  onDidWriteToStdout: (callback) ->
     @emitter.on 'did-write-to-stdout', callback

   stderrFunc: (output) =>
     @emitter.emit 'did-write-to-stderr', { message: output }

-  onDidWriteToStderr: (callback) =>
+  onDidWriteToStderr: (callback) ->
     @emitter.on 'did-write-to-stderr', callback

   destroy: ->
@@ -66,7 +67,7 @@

     @emitter.emit 'did-exit', { executionTime: executionTime, returnCode: returnCode }

-  onDidExit: (callback) =>
+  onDidExit: (callback) ->
     @emitter.on 'did-exit', callback

   createOnErrorFunc: (command) =>
@@ -75,7 +76,7 @@
       @emitter.emit 'did-not-run', { command: command }
       nodeError.handle()

-  onDidNotRun: (callback) =>
+  onDidNotRun: (callback) ->
     @emitter.on 'did-not-run', callback

   options: ->


Not extracting method per @markijbema's comment

--- js-library/app/js/views/balloon.coffee
+++ js-library/app/js/views/balloon.coffee
@@ -5,9 +5,6 @@
     @_loading = false
     @_fact = fact

-    @_initializeTemplate()
-
-  _initializeTemplate: ->
     @$el = $(Factlink.templates.indicator)
     @$el.appendTo(Factlink.el)
     @$el.bind "mouseenter", => @_fact.focus()


Need to clone button

Need to clone button

--- SingularityUI/app/models/Request.coffee
+++ SingularityUI/app/models/Request.coffee
@@ -89,7 +89,7 @@
         vex.dialog.prompt
             message: runTemplate id: @get "id"
             buttons: [
-                $.extend vex.dialog.buttons.YES, text: 'Run now'
+                $.extend _.clone(vex.dialog.buttons.YES), text: 'Run now'
                 vex.dialog.buttons.NO
             ]
             callback: (data) =>


Wrong key

Wrong key

--- resources/assets/coffee/react/beatmap-discussions/new-discussion.coffee
+++ resources/assets/coffee/react/beatmap-discussions/new-discussion.coffee
@@ -98,7 +98,7 @@
             span
               className: buttonCssClasses
               onClick: @toggleSticky
-              title: if @state.sticky then osu.trans('beatmap.discussions.new.pinned') else osu.trans('beatmap.discussions.new.unpinned')
+              title: if @state.sticky then osu.trans('beatmaps.discussions.new.pinned') else osu.trans('beatmaps.discussions.new.unpinned')

               span className: 'btn-circle__content',
                 i className: 'fas fa-thumbtack'


Removed expression as a transient quality for equality feature -- confusing

--- javascripts/lib/math/expression_equality.coffee
+++ javascripts/lib/math/expression_equality.coffee
@@ -31,14 +31,9 @@
         else
           false

-      isNonExpressionEqual: (other)->
-        @size() == 1 && ref.refine(@first()).isEqual(other)
-
       isEqual: (other)->
         if other instanceof comps.expression
           @isExpressionEqual(other)
-        else
-          @isNonExpressionEqual(other)
     })

     ref.forType(comps.number, {


Change base64.coffee

Change base64.coffee

--- scripts/base64.coffee
+++ scripts/base64.coffee
@@ -19,8 +19,8 @@
 #   https://github.com/snowbit/snowbit/blob/master/NOTICE

 module.exports = (robot) ->
-  robot.respond /base64 -e( me)? (.*)/i, (msg) ->
+  robot.respond /base64( |　)-e( me)? (.*)/i, (msg) ->
     msg.reply new Buffer(msg.match[2]).toString('base64')

-  robot.respond /base64 -d( me)? (.*)/i, (msg) ->
+  robot.respond /base64( |　)-d( me)? (.*)/i, (msg) ->
     msg.reply new Buffer(msg.match[2], 'base64').toString('utf8')


use time as part of update calculations to smooth movement

--- scrolling/player-controller.coffee
+++ scrolling/player-controller.coffee
@@ -9,8 +9,8 @@
     xVelocity: 0
     yVelocity: 0

-    accelerationStep: 0.2
-    accelerationCap: 5
+    accelerationStep: 30
+    accelerationCap: 7

     hitBox: null

@@ -27,22 +27,22 @@

         stage.addChild @hitBox

-    accelerateRight: ->
-        @xVelocity += @accelerationStep
+    accelerateRight: (timeRatio) ->
+        @xVelocity += @accelerationStep * timeRatio
         @capVelocity()

-    accelerateLeft: ->
-        @xVelocity -= @accelerationStep
+    accelerateLeft: (timeRatio) ->
+        @xVelocity -= @accelerationStep * timeRatio
         @capVelocity()

-    slow: ->
+    slow: (timeRatio) ->
         if @xVelocity < 0
-            @xVelocity += @accelerationStep
+            @xVelocity += @accelerationStep * timeRatio
             if @xVelocity > 0
                 @xVelocity = 0

         else if @xVelocity > 0
-            @xVelocity -= @accelerationStep
+            @xVelocity -= @accelerationStep * timeRatio
             if @xVelocity < 0
                 @xVelocity = 0

@@ -54,13 +54,15 @@
             @xVelocity = @accelerationCap

     update: (elapsedTime, inputState) ->
+        timeRatio = elapsedTime / 1000
+
         if inputState.right
-            @accelerateRight()
+            @accelerateRight timeRatio

         else if inputState.left
-            @accelerateLeft()
+            @accelerateLeft timeRatio

         else
-            @player.slow()
+            @slow timeRatio

         @hitBox.position.x += @xVelocity


debugging google groups

debugging google groups

--- src/adaptor/google_group.coffee
+++ src/adaptor/google_group.coffee
@@ -1,17 +1,19 @@
-coffee = require 'coffee-script'
 Nightmare = require 'nightmare'
 { log, p } = require 'lightsaber'

 class GoogleGroup
+  topicSelector = '.LOFA24-rb-h'
+
   read: (groupName) ->
     browser = new Nightmare
     url = "https://groups.google.com/forum/#!forum/#{groupName}"
     browser
     .goto url
-    .wait() # '.LOFA24-p-Q'
+    .wait() # topicSelector (multiple?)
+    .screenshot "tmp/#{groupName}" #"-#{(new Date).toISOString()}.png")
     .evaluate \
-          (-> document.querySelectorAll '.LOFA24-p-Q'),
-          (links) -> (log link?.href) for link in links
+          (-> document.querySelectorAll '.LOFA24-rb-h'),
+          (links) -> log links #(log link.href) for link in links
     .run (err) ->
       if err
         return console.log(err)


Debug

Debug

--- scripts/mailin.coffee
+++ scripts/mailin.coffee
@@ -39,6 +39,7 @@
     res.end "OK"

   post = (message, attachments...) ->
+    console.log message
     robot.logger.info "Post to hubot hook via mailin script"

     path = "/services/hooks/hubot"


better test for flatmap

better test for flatmap

--- spec/BaconSpec.coffee
+++ spec/BaconSpec.coffee
@@ -39,8 +39,8 @@
   it "should spawn new stream for each value and collect results into a single stream", ->
     expectStreamEvents(
       -> repeat(10, [1, 2]).take(2).flatMap (value) ->
-        Bacon.later(10, value)
-      [1, 2])
+        Bacon.sequentially(100, [value, value])
+      [1, 2, 1, 2])

 describe "merge", ->
   it "merges two streams and ends when both are exhausted", ->


be explicit about node modules

be explicit about node modules

--- scripts/server.coffee
+++ scripts/server.coffee
@@ -1,8 +1,11 @@
 fs = require 'fs'
 coffee = require 'coffee-script'
+express = require 'express'
 {TextMessage} = require 'hubot'

 module.exports = (robot) ->
+  robot.router.use('/node_components', express.static(__dirname + '../node_components'));
+
   io = require('socket.io').listen(robot.server)
   io.sockets.on 'connection', (socket) ->


CUI.Events

CUI.Events

- Fix for Events.wait: When a "wait" timeout tried to destroy a Listener it could
  happen that the Listener was already destroyed. Before destroying a listener,
  check if it has already been destroyed.

--- src/base/Events/Listener.coffee
+++ src/base/Events/Listener.coffee
@@ -126,6 +126,9 @@

 	destroy: ->
+		if @isDestroyed()
+			return
+
 		Events.unregisterListener(@)

 		for _type in @getTypes()


order + hide menu links

order + hide menu links

--- js/msa/menu/defaultmenu.coffee
+++ js/msa/menu/defaultmenu.coffee
@@ -25,7 +25,7 @@
         else
           #$(this).children().first().text "Hide Marker"
           @msa.visibleElements.ruler = true
-        @msa.redrawEntire()
+        @msa.redrawContainer()

       menuFile.addNode "Hide Labels", =>
         if @msa.config.visibleElements.labels is true
@@ -34,7 +34,7 @@
         else
           @msa.config.visibleElements.labels = true
           #$(this).children().first().text "Hide Labels"
-        @msa.redrawEntire()
+        @msa.redrawContainer()

       menuFile.addNode "Hide Menu", =>
         @deleteMenu()
@@ -106,15 +106,15 @@
     _createOrderingMenu: ->
       menuOrdering = new MenuBuilder("Ordering")
       menuOrdering.addNode "ID", =>
-        @msa.ordering.orderSeqsAfterScheme "numeric"
+        @msa.ordering.setOrdering "numeric"

       menuOrdering.addNode "ID Desc", =>
-        @msa.ordering.orderSeqsAfterScheme "reverse-numeric"
+        @msa.ordering.setOrdering "reverse-numeric"

       menuOrdering.addNode "Label", =>
-        @msa.ordering.orderSeqsAfterScheme "alphabetic"
+        @msa.ordering.setOrdering "alphabetic"

       menuOrdering.addNode "Label Desc", =>
-        @msa.ordering.orderSeqsAfterScheme "reverse-alphabetic"
+        @msa.ordering.setOrdering "reverse-alphabetic"

       menuOrdering.buildDOM()


Changes to be committed: 	modified:   src/browser/nylas-window.coffee

--- src/browser/nylas-window.coffee
+++ src/browser/nylas-window.coffee
@@ -54,7 +54,7 @@

     options =
       show: false
-      title: title ? 'Nylas'
+      title: title ? 'Nylas N1'
       frame: frame
       width: width
       height: height


Set feedId of existing articles to id of promed feed on startup

--- server/startup.coffee
+++ server/startup.coffee
@@ -96,6 +96,23 @@
               id: "userSpecifiedDisease:#{incident.disease}"
               text: "Other Disease: #{incident.disease}"

+  promedFeedId = Feeds.findOne(title: 'ProMED-mail')?._id
+
+  if not promedFeedId
+    promedFeed =
+      title: 'ProMED-mail'
+      url: 'promedmail.org/post/'
+      addedDate: new Date()
+    feedSchema.validate(promedFeed)
+    promedFeedId = Feeds.insert(promedFeed)
+
+  Articles.find(
+    url: $regex: /promedmail.org/
+    feed: $exists: false
+  ).forEach (article) ->
+    Articles.update article._id,
+      $set: feedId: promedFeedId
+
   CuratorSources.find().forEach (source) ->
     url = "promedmail.org/post/#{source._sourceId}"
     article =
@@ -106,14 +123,6 @@
       publishDateTZ: "EDT"
       title: source.title
       reviewed: source.reviewed
-      feed: "promed-mail"
+      feedId: promedFeedId
     articleSchema.validate(article)
     Articles.upsert(article._id, article)
-
-  if not Feeds.findOne(title: 'ProMED-mail')
-    promedFeed =
-      title: 'ProMED-mail'
-      url: 'https://www.promedmail.org/'
-      addedDate: new Date()
-    feedSchema.validate(promedFeed)
-    Feeds.insert(promedFeed)


Replace datepicker everytime!

Replace datepicker everytime!

--- app/assets/javascripts/date_picker.coffee
+++ app/assets/javascripts/date_picker.coffee
@@ -7,6 +7,7 @@
   events: (add) ->
     add('.input-date', 'changeDate', (el, e) -> @update_select_values(el))
     add('.input-date', 'keyup', (el, e) -> @update_select_values(el))
+    add('.input-date', 'click', (el) -> $(el).datepicker('place'))

   init: ->
     super


:lipstick:

:lipstick:

--- src/app/buffer.coffee
+++ src/app/buffer.coffee
@@ -117,7 +117,7 @@
   getTextInRange: (range) ->
     range = Range.fromObject(range)
     if range.start.row == range.end.row
-      return @lines[range.start.row][range.start.column...range.end.column]
+      return @lineForRow(range.start.row)[range.start.column...range.end.column]

     multipleLines = []
     multipleLines.push @lineForRow(range.start.row)[range.start.column..] # first line


array sort fallbacks when vars is supplied

--- src/array/sort.coffee
+++ src/array/sort.coffee
@@ -4,12 +4,18 @@
 # Sorts an array of objects
 module.exports = (arr, keys, sort, colors, vars, depth) ->

-  if not arr or arr.length <= 1 or not keys
+  if not arr or arr.length <= 1
     arr or []
   else

     if vars
+
+      keys = vars.order.value or vars.size.value or vars.id.value unless keys
+      sort = vars.order.sort.value unless sort
+      colors = vars.color.value or [] unless colors
+
       for d in arr
-        d.d3plus.sortKeys = fetchSort(vars, d, keys, colors, depth) if d.d3plus
+        d.d3plus = {} unless d.d3plus
+        d.d3plus.sortKeys = fetchSort vars, d, keys, colors, depth

     arr.sort (a, b) -> comparator a, b, keys, sort, colors, vars, depth


閉じるボタンを追加

閉じるボタンを追加

--- app/assets/javascripts/projects.js.coffee
+++ app/assets/javascripts/projects.js.coffee
@@ -30,6 +30,7 @@
       title: $(this).parent().find(".edit_title").val(),
       content: $(this).parent().find(".edit_content").val()
     } })
+    location.reload()

   update = (element, data) ->
     task_id = $($(element).parents("tr")[0]).attr("data-task-id")
@@ -45,7 +46,13 @@
     content_label = $("<label>", { text: "内容" })
     content = $("<textarea>", { rows: 5, text: $(this).parent().find(".feed_item_content").val(), class: "form-control edit_content" })
     button = $("<a>", { class: "btn btn-primary task_update_button" }).append("更新")
+    panel_close_button = $("<button>", { type: "button", class: "close", text: "×", id: "edit_panel_close" })
+    panel_header = $("<div>", { class: "panel-heading" })
+    panel_header.append(panel_close_button)
     panel_body = $("<div>", { class: "panel-body form-group" })
     panel_body.append(title_label, title, content_label, content, button)
-    edit_panel = $("<div>", { class: "panel edit_panel" }).append(panel_body)
+    edit_panel = $("<div>", { class: "panel edit_panel" }).append(panel_header, panel_body)
     $(this).after(edit_panel)
+
+  $(document).on "click", "#edit_panel_close", ->
+    $('.edit_panel').fadeOut()


Refactor reload to handle associations

Refactor reload to handle associations

--- src/attributes.coffee
+++ src/attributes.coffee
@@ -65,7 +65,11 @@
     resource = this
     ActiveResource.interface.get(@links()['self'], @__queryOptions)
     .then (reloaded) ->
-      resource.assignAttributes(reloaded)
+      resource.assignAttributes(reloaded.attributes())
+      resource.klass().reflectOnAllAssociations().each (reflection) ->
+        target = reloaded.association(reflection.name).reader()
+        target = target.toArray() if reflection.collection?()
+        resource.association(reflection.name).writer(target, false)

   # private


* [bug] assign stream contents should check objectMode

--- src/attributes.coffee
+++ src/attributes.coffee
@@ -7,6 +7,7 @@
 stream          = require 'stream'
 Stream          = stream.Stream
 PassThrough     = stream.PassThrough
+isStream        = (aStream)->aStream instanceof Stream

 module.exports =
   cwd:
@@ -45,9 +46,11 @@
   stat: null
   contents:
     assign: (value, dest, src, name)->
-      if value instanceof Stream
-        t = value.pipe(new PassThrough())
-        src.contents = value.pipe(new PassThrough())
+      if isStream value
+        opts =
+          objectMode: value._readableState.objectMode
+        t = value.pipe(new PassThrough(opts))
+        src.contents = value.pipe(new PassThrough(opts))
         value = t
       value
   # the skipped length from beginning of contents.


Hide publish/unpublish buttons.

Hide publish/unpublish buttons.

--- app/client/js/react/pages/editsong.coffee
+++ app/client/js/react/pages/editsong.coffee
@@ -94,21 +94,21 @@
                 button
                   className: 'btn btn-default'
                 , EditSong.MSG_ADD_NEW_SONG
-              if editMode && !song.inTrash then [
-                button
-                  className: 'btn btn-default'
-                  key: 'publish'
-                  onTap: @onPublishTap
-                  type: 'button'
-                , EditSong.MSG_PUBLISH
-                if song.isPublished()
-                  button
-                    className: 'btn btn-default'
-                    key: 'unpublish'
-                    onTap: @onUnpublishTap
-                    type: 'button'
-                  , EditSong.MSG_UNPUBLISH
-              ]
+              # if editMode && !song.inTrash then [
+              #   button
+              #     className: 'btn btn-default'
+              #     key: 'publish'
+              #     onTap: @onPublishTap
+              #     type: 'button'
+              #   , EditSong.MSG_PUBLISH
+              #   if song.isPublished()
+              #     button
+              #       className: 'btn btn-default'
+              #       key: 'unpublish'
+              #       onTap: @onUnpublishTap
+              #       type: 'button'
+              #     , EditSong.MSG_UNPUBLISH
+              # ]
               if editMode
                 button
                   className: "btn btn-#{if song.inTrash then 'default' else 'danger'}"


Remove some console logging

Remove some console logging

--- app/js/jail_iframe/classes/buttons.coffee
+++ app/js/jail_iframe/classes/buttons.coffee
@@ -19,13 +19,11 @@

   show: =>
     @stopLoading()
-    console.log @el.className
     FactlinkJailRoot.set_position_of_element @_top, @_left, window, @frame.$el
     #TODO:what's this line do?
     #FactlinkJailRoot.$factlinkCoreContainer.find('div.fl-button').removeClass('active')
     @$el.addClass 'active'
     @frame.$el.addClass 'factlink-control-visible'
-    console.log @el.className

   hide: =>
     @$el.removeClass 'active'


Use addClass/removeClass instead of setStateClass

--- app/js/jail_iframe/classes/buttons.coffee
+++ app/js/jail_iframe/classes/buttons.coffee
@@ -3,27 +3,26 @@
     @frame = new FactlinkJailRoot.ControlIframe()
     @frame.setContent($.parseHTML(@content.trim())[0])
     @$el = $(@frame.frameBody.firstChild)
-    @$el.on 'mouseenter', => @setStateClass 'fl-button-state-hovered'
+    @$el.on 'mouseenter', => @_addClass 'fl-button-state-hovered'

     for event, callback of dom_events
       @$el.on event, callback

-  startLoading: => @setStateClass 'fl-button-state-loading'
+  startLoading: => @_addClass 'fl-button-state-loading'
+  stopLoading: => @_removeClass 'fl-button-state-loading'

-  setStateClass: (stateClass) =>
-    @$el.removeClass "fl-button-state-loading fl-button-state-hovered"
-    @$el.addClass stateClass
+  _addClass: (classes) =>
+    @$el.addClass classes
     @frame.sizeFrameToFitContent()

-  _removeStateClass: (stateClass) =>
-    @$el.removeClass stateClass
+  _removeClass: (classes) =>
+    @$el.removeClass classes
     @frame.sizeFrameToFitContent()

   _showAtCoordinates: (top, left) =>
     return if @_visible

     @_visible = true
-    @setStateClass ''
     @frame.fadeIn()

     containerOffset = FactlinkJailRoot.$factlinkCoreContainer.offset()
@@ -56,7 +55,7 @@

   constructor: ->
     super
-    @$el.on 'mouseleave', => @_removeStateClass 'fl-button-state-hovered'
+    @$el.on 'mouseleave', => @_removeClass 'fl-button-state-hovered'

   _textContainer: ($el) ->
     for el in $el.parents()


Update comments.

Update comments.

--- src/blueprints.coffee
+++ src/blueprints.coffee
@@ -13,6 +13,7 @@
       uri

 blueprints =
+  # ACCOUNT
   account:
     get:
       uri:     '/account'
@@ -48,7 +49,8 @@
         @setUserKey res.data.token
         res

-    logout: -> @setUserKey ''
+    logout: ->
+      @setUserKey ''

     reset:
       uri:     (x) -> '/account/reset?email=' + x.email


add TODO

add TODO

--- src/components.coffee
+++ src/components.coffee
@@ -71,10 +71,12 @@

     constructor: (cellClass = Object, width = 0, height = 0) ->

+        # TODO: remove dependency of underscore
         @row = _.arrayInit height, ->
             _.arrayInit width, ->
                 new cellClass

+        # TODO: remove dependency of underscore
         @col = _.arrayInit width, (i) =>
             _.arrayInit height, (j) =>
                 @row[j][i]
@@ -196,6 +198,7 @@
         x: item.range.markX item.point.x
         y: item.range.markY item.point.y

+    # TODO: remove dependency of underscore
     getBitAdd = _.memoize(

         (a, b) ->


Small refactor

Small refactor

--- src/controller.coffee
+++ src/controller.coffee
@@ -25,10 +25,11 @@
     @queues[name] ?= new (Config.fetchQueue(name))(@)

   request: (url, options = {}) =>
-    queue = @queue = @fetchQueue(options.queue || 'sync')
     options = Utils.reverseMerge options,
       pushState: true
+      queue: 'sync'

+    queue = @queue = @fetchQueue(options.queue)
     url = new ComponentUrl url

     if url.crossOrigin()
@@ -69,9 +70,6 @@
   cache: (key, value) =>
     return @atomCache[key] if value == null
     @atomCache[key] ||= value
-
-  # Events
-  onLoadEnd: => @http = null

   onLoad: (xhr, url, options) =>
     options.onRequestEnd?(url.absolute)


Add an attribute that allows hover divs to be addressed by data-id

--- src/controller.coffee
+++ src/controller.coffee
@@ -1830,6 +1830,9 @@
     hoverDiv.className = 'droplet-hover-div'

     hoverDiv.title = data.title ? block.stringify()
+
+    if data.id?
+      hoverDiv.setAttribute 'data-id', data.id

     bounds = @paletteView.getViewNodeFor(block).totalBounds


Fix font-family set bug

Fix font-family set bug

--- src/controller.coffee
+++ src/controller.coffee
@@ -2443,7 +2443,7 @@

         div.innerText = textElement.model.value

-        div.style.font = @fontSize + 'px Courier New'
+        div.style.font = @fontSize + 'px ' + @fontFamily

         div.style.left = "#{textElement.bounds[0].x - @scrollOffsets.main.x}px"
         div.style.top = "#{textElement.bounds[0].y - @scrollOffsets.main.y}px"
@@ -2476,7 +2476,7 @@
         div.style.left = 0
         div.style.top = "#{treeView.bounds[line].y + treeView.distanceToBase[line].above - @fontSize}px"

-        div.style.font = @fontSize + 'px Courier New'
+        div.style.font = @fontSize + 'px ' + @fontFamily
         div.style.width = "#{@gutter.offsetWidth}px"
         translatingElements.push div

@@ -2575,7 +2575,7 @@

           div.innerText = textElement.model.value

-          div.style.font = @fontSize + 'px Courier New'
+          div.style.font = @fontSize + 'px ' + @fontFamily
           div.style.position = 'absolute'

           div.style.left = "#{textElement.bounds[0].x - @scrollOffsets.main.x + translationVectors[i].x}px"
@@ -2607,7 +2607,7 @@

           div.innerText = line + 1

-          div.style.font = @fontSize + 'px Courier New'
+          div.style.font = @fontSize + 'px ' + @fontFamily
           div.style.width = "#{@aceEditor.renderer.$gutter.offsetWidth}px"

           div.style.left = 0
@@ -2761,6 +2761,8 @@
   Editor::setFontFamily = (fontFamily) ->
     draw._setGlobalFontFamily fontFamily

+    @fontFamily = fontFamily
+
     @view.clearCache(); @dragView.clearCache()
     @gutter.style.fontFamily = fontFamily


Closes #68; Added a quick usage warning to the option docs.

--- src/core/chart.coffee
+++ src/core/chart.coffee
@@ -86,6 +86,9 @@
   #
chart.option(key, value) - Sets an option and triggers the associated event.

   #
chart.option(object) - Allows for the setting of multiple options at once.

   #

+  #
+  # Warning: This method is currently experimental, and is not used by any core part of the library.
+  #   I'd feel much safer with real unit tests in place before proceeding to build around it.
   option: ->
     # No Arguments: Return a copy of this chart's options
     if arguments.length == 0


[DOC] Add documentation for core/stats.coffee.

--- src/core/stats.coffee
+++ src/core/stats.coffee
@@ -28,6 +28,8 @@
     renderStats : (res, options) =>
         res.render 'stats', options

+    # Create a new document into the collection "stat".
+    # Set the nb_user_tmp to 1.
     insertNewDate : (req, res, body, callback, timestamp) =>
         Request.post {
             json    :
@@ -40,6 +42,7 @@
             else
                 callback req, res

+    # Increment the nb_user_tmp into the stat collection for the given id from the body.
     updateNbrTmpUser : (req, res, body, callback) =>
         body = body[0]
         body.nb_user_tmp += 1
@@ -52,6 +55,7 @@
             else
                 callback req, res

+    # Create or update the number of temporary user daily.
     countTmpUser : (req, res, callback) =>
         date = Moment().format "YYYY-MM-DD"
         timestamp = String(do (new Date(date)).getTime)
@@ -65,6 +69,7 @@
                 else
                     @updateNbrTmpUser req, res, body, callback

+    # Parse the number of room created per Day.
     getNumberOfRoomsPerDay: (req, res, body) =>
         body = JSON.parse body
         data = {}
@@ -81,6 +86,8 @@
             tmp.push [Number(timestamp), nb]
         return tmp

+    # Get the number of temporary user created per Day.
+    # Return a array of array in the form [[2013-03-12,42], [2013-03-13,12]].
     getNumberOfTmpUsersPerDay: (req, res, body) =>
         body = JSON.parse body
         data = []
@@ -88,6 +95,7 @@
             data.push [Number(users.c_date), users.nb_user_tmp]
         return data

+    # Get all the values for the graphs and render the stats page if no errors occurs.
     statsPage : (req, res) =>
         Request.get "#{Config.Restapi.Url}/room", (e, r, body) =>
             if e? or r.statusCode > 200


better next team picking algorithm

better next team picking algorithm

--- models/person.coffee
+++ models/person.coffee
@@ -193,9 +193,6 @@
     # non-technical judges don't see technical entries
     filter.technical = ($ne: true) if not @technical

-    # judges focus on good stuff
-    #filter['scores.overall'] = ($gt: 15)
-
     # judges only see entries with pitch videos
     filter['entry.videoURL'] = /./

@@ -205,12 +202,28 @@
   sort.push ['updatedAt', -1]

   Team = mongoose.model 'Team'
+  Vote = mongoose.model 'Vote'
   @votedOnTeamIds (err, votedOn) =>
     next err if err

+
+    if @judge and votedOn.length < 15
+      # judges focus on good stuff
+      filter['$or'] = [
+        'scores.judge_count': { $lt: 4 }
+        'scores.judge_utility': { $gt: 3 }
+        'scores.judge_design': { $gt: 3 }
+        'scores.judge_innovation': { $gt: 3 }
+        'scores.judge_completeness': { $gt: 3 }
+      ]
+
+
     # every third vote should be for something good
-    # if votedOn.length % 3 is 0
-    #   sort.unshift ['scores.overall', -1]
+    if votedOn.length % 3 is 0
+      dimensions = Vote.dimensions.concat('overall')
+      dimension = dimensions[Math.floor(Math.random() * dimensions.length)]
+
+      sort.unshift ["scores.#{dimension}", -1]

     # not already voted on or skipped
     filter._id = $nin: votedOn.concat @skippedTeamIds


improve language test output (just a little bit)

before:
```
  1) The sloc module language support should support coffee:

      AssertionError: expected 6 to equal 5
      + expected - actual

      +5
      -6

    at Context.<anonymous> (D:\git\sloc\spec\sloc.spec.coffee:41:33)
    [...]
```

after:
```
  1) The sloc module language support should support coffee:

      Comment
      + expected - actual

      +5
      -6

    at Context.<anonymous> (D:\git\sloc\spec\sloc.spec.coffee:41:33)
    [...]
```

So you know what count is wrong without looking it up in `sloc.spec.coffee`.

--- spec/sloc.spec.coffee
+++ spec/sloc.spec.coffee
@@ -34,19 +34,19 @@
           res = sloc l.code, n
           (n in sloc.extensions).should.equal true
           if l.total
-            res.total   .should.equal l.total
+            res.total   .should.equal l.total,   "Total"
           if l.source
-            res.source  .should.equal l.source
+            res.source  .should.equal l.source,  "Source"
           if l.comment
-            res.comment .should.equal l.comment
+            res.comment .should.equal l.comment, "Comment"
           if l.single
-            res.single  .should.equal l.single
+            res.single  .should.equal l.single,  "Single"
           if l.block
-            res.block   .should.equal l.block
+            res.block   .should.equal l.block,   "Block"
           if l.empty
-            res.empty   .should.equal l.empty
+            res.empty   .should.equal l.empty,   "Empty"
           if l.mixed
-            res.mixed   .should.equal l.mixed
+            res.mixed   .should.equal l.mixed,   "Mixed"

   it "should throw an error", ->
     (-> sloc "foo", "foobar").should.throw()


:white_check_mark: adding spec tests for opening in new window

--- spec/tabs-spec.coffee
+++ spec/tabs-spec.coffee
@@ -410,6 +410,20 @@
         expect(atom.workspace.getPanes()[0]).toBe pane
         expect(atom.workspace.getPanes()[1].getItems()[0].getTitle()).toBe item2.getTitle()

+    describe "when tabs:open-in-new-window is fired", ->
+      it "opens new window, closes current tab", ->
+        triggerMouseDownEvent(tabBar.tabForItem(item2), which: 3)
+        expect(atom.workspace.getPanes().length).toBe 1
+
+        spyOn(atom,'open')
+        atom.commands.dispatch(tabBar.element, 'tabs:open-in-new-window')
+        expect(atom.open).toHaveBeenCalled()
+
+        expect(pane.getItems().length).toBe 2
+        expect(tabBar.getTabs().length).toBe 2
+        expect(tabBar.find('.tab:contains(Item 2)')).not.toExist()
+        expect(tabBar.find('.tab:contains(Item 1)')).toExist()
+
   describe "dragging and dropping tabs", ->
     describe "when a tab is dragged within the same pane", ->
       describe "when it is dropped on tab that's later in the list", ->


Don't return choice

Don't return choice

--- spec/Thenable.coffee
+++ spec/Thenable.coffee
@@ -256,6 +256,7 @@
                 btree.deliver data
                 btree.then 'btreethen', (c, data) ->
                   data * 3
+              return
         t = Root()
         t.deliver 5
         .contest "contest-multiply", [


:lipstick: Add missing ")".

:lipstick: Add missing ")".

--- spec/git-spec.coffee
+++ spec/git-spec.coffee
@@ -16,7 +16,7 @@

   describe "@open(path)", ->
     it "returns null when no repository is found", ->
-      expect(Git.open(path.join(temp.dir, 'nogit.txt')).toBeNull()
+      expect(Git.open(path.join(temp.dir, 'nogit.txt'))).toBeNull()

   describe "new Git(path)", ->
     it "throws an exception when no repository is found", ->


using @ was risky.  Better to have an explicit exports object that we can call from our private functions

--- server/authentication/index.coffee
+++ server/authentication/index.coffee
@@ -36,7 +36,7 @@
       res.json 500, {message: 'host not found on request object'}

   publicAction = (req, res, next) ->
-    @_systemCheck req, res, next
+    exports._systemCheck req, res, next

   hmacAuth = (req, res, next) ->
     if _.indexOf(options.strategies, 'Hmac') is -1
@@ -68,15 +68,15 @@
       )(req, res, next)

   userAction = (req, res, next) ->
-    @publicAction req, res, () =>
+    exports.publicAction req, res, () =>
       if req.isAuthenticated()
         permissionsMiddleware req, res, next
       else
-        @_hmacAuth req, res, (err, user) =>
+        exports._hmacAuth req, res, (err, user) =>
           if user and (not err)
             permissionsMiddleware req, res, next
           else
-            @_playAuth req, res, (err, user) ->
+            exports._playAuth req, res, (err, user) ->
               if user and (not err)
                 permissionsMiddleware req, res, next
               else
@@ -132,11 +132,14 @@
     if _.indexOf(options.strategies, 'Facebook') > -1
       facebook.routes app, publicAction

+  exports =
   #Export the authentiaction action middleware
-  publicAction: publicAction
-  userAction: userAction
-  adminAction: adminAction
-  sysAdminAction: sysAdminAction
-  _systemCheck: systemCheck
-  _hmacAuth: hmacAuth
-  _playAuth: playAuth+    publicAction: publicAction
+    userAction: userAction
+    adminAction: adminAction
+    sysAdminAction: sysAdminAction
+    _systemCheck: systemCheck
+    _hmacAuth: hmacAuth
+    _playAuth: playAuth
+
+  exports


set headers for binary downloads

set headers for binary downloads

--- server/controllers/binaries.coffee
+++ server/controllers/binaries.coffee
@@ -114,26 +114,39 @@
 # GET /data/:id/binaries/:name/
 # Download a the file attached to the binary object.
 module.exports.get = (req, res, next) ->
+    name = req.params.name
+    binary = req.doc.binary

-    name = req.params.name
-    if req.doc.binary and req.doc.binary[name]
+    if binary and binary[name]

         # Build stream for fetching file from the database. Use a custom lib
         # instead of cradle to avoid too high memory consumption.
-        id = req.doc.binary[name].id
-        stream = downloader.download id, name, (err, stream) ->
-            if err and err.error = "not_found"
-                err = new Error "not found"
-                err.status = 404
-                next err
-            else if err
-                next new Error err.error
+        id = binary[name].id

-            if req.headers['range']?
-                stream.setHeader 'range', req.headers['range']

-            # Use streaming to avoid high memory consumption.
-            stream.pipe res
+        db.get id, (err, binDoc) ->
+            return next err if err
+
+            # Set response header from attachment infos
+            length = binDoc._attachments[name].length
+            type = binDoc._attachments[name]['content-type']
+            res.setHeader 'Content-Length', length
+            res.setHeader 'Content-Type', type
+
+            # Run the downlaod with Node low level api.
+            stream = downloader.download id, name, (err, stream) ->
+                if err and err.error == "not_found"
+                    err = new Error "not found"
+                    err.status = 404
+                    next err
+                else if err
+                    next new Error err.error
+
+                if req.headers['range']?
+                    stream.setHeader 'range', req.headers['range']
+
+                # Use streaming to avoid high memory consumption.
+                stream.pipe res

     # No binary found, error is returned.
     else


Attempted fix for build timeout

Attempted fix for build timeout

--- spec/sqf-spec.coffee
+++ spec/sqf-spec.coffee
@@ -2,10 +2,22 @@
   grammar = null

   beforeEach ->
+    atom.project.setPaths([path.join(__dirname, 'sample')])
+    workspaceElement = atom.views.getView(atom.workspace)
+
+    ## Open our sample init.sqf file
     waitsForPromise ->
-      atom.packages.activatePackage("language-arma-atom")
+      atom.workspace.open('init.sqf')
+
+    ## active our sqf package
+    #waitsForPromise ->
+    #  atom.packages.activatePackage("language-arma-atom")

     runs ->
+      activationPromise = atom.packages.activatePackage('language-arma-atom')
+      activationPromise.fail (reason) ->
+        throw reason
+
       grammar = atom.grammars.grammarForScopeName("source.sqf")

   it "parses the grammar", ->


Revert ajax abort change in issue #28.

Revert ajax abort change in issue #28.

--- src/coffee/async/promise.coffee
+++ src/coffee/async/promise.coffee
@@ -7,9 +7,7 @@
   # See also [Promise.resolve](https://github.com/petkaantonov/bluebird/blob/master/API.md).
   OJ.async.register 'ajaxPromise', (ajax) ->
     promise = Promise.resolve ajax
-    promise.abort ->
-      ajax.abort()
-      promise.done()
+    promise.abort = ajax.abort
     promise.readyState = ajax.readyState
     promise


add an (unstyled) ui for displaying and modifying the dimensions of the grid

--- src/coffee/client/puzzle.coffee
+++ src/coffee/client/puzzle.coffee
@@ -262,7 +262,6 @@
                 op = Ot.compose(@state.puzzle, op, Ot.opEditCellValue row, col, "contents", "")
                 op = Ot.compose(@state.puzzle, op, Ot.opEditCellValue row, col, "number", null)
                 op = Ot.compose(@state.puzzle, op, Ot.opEditCellValue row, col, "open", true)
-        console.log(op)
         @props.requestOp op

     # Perform an automatic renumbering.
@@ -652,6 +651,32 @@

         @closeMatchFinder()

+    # setting dimensions
+    onSetDimensions: (width, height) ->
+        # NOTE: this actually has one pretty annoying consequence: if the dimensions
+        # are, say, 15x15 and two users set them to 20x20 at the same time, each user
+        # will add 5 rows and 5 cols, so the dimension will end up at 25x25 when they
+        # probably just wanted 20x20. I guess this is a symptom of the row/col OT
+        # being overly sophisticated? Well, we could fix this at the OT layer. (TODO)
+
+        op = Ot.identity(@state.puzzle)
+
+        if width < @width()
+            op = Ot.compose(@state.puzzle, op,
+                    Ot.opDeleteCols(@state.puzzle, width, @width() - width))
+        else if width > @width()
+            op = Ot.compose(@state.puzzle, op,
+                    Ot.opInsertCols(@state.puzzle, @width(), width - @width()))
+
+        if height < @height()
+            op = Ot.compose(@state.puzzle, op,
+                    Ot.opDeleteRows(@state.puzzle, height, @height() - height))
+        else if height > @height()
+            op = Ot.compose(@state.puzzle, op,
+                    Ot.opInsertRows(@state.puzzle, @height(), height - @height()))
+
+        @props.requestOp op
+
     # Copy/cut/paste stuff

     doCopy: (event) ->
@@ -735,6 +760,9 @@
                             onMatchFinderChoose={@onMatchFinderChoose}
                             onMatchFinderClose={@closeMatchFinder}
                             renumber={@renumber}
+                            width={@state.puzzle.width}
+                            height={@state.puzzle.height}
+                            onSetDimensions={@onSetDimensions}
                             needToRenumber={@needToRenumber()}
                             toggleMaintainRotationalSymmetry={@toggleMaintainRotationalSymmetry}
                             maintainRotationalSymmetry={@state.maintainRotationalSymmetry}
@@ -799,6 +827,11 @@
                 onClose={@props.onMatchFinderClose} />
         else

+                <DimensionWidget
+                    width={@props.width}
+                    height={@props.height}
+                    onSet={@props.onSetDimensions}
+                    />

                   <input type="button" value="Re-assign numbers" onClick={@props.renumber}
                         title="Sets the numbers in the grid based off of the locations of the black cells, according to standard crossword rules."
@@ -851,6 +884,83 @@

+DimensionWidget = React.createClass
+    # props:
+    #   width
+    #   height
+    #   onSet(width, height)
+    getInitialState: () ->
+        isEditing: false
+
+    mixins: [React.addons.PureRenderMixin]
+
+    render: () ->
+        if @state.isEditing
+

+                Width:
+                <input type="text" defaultValue={@props.width} onKeyDown={@onKeyDown} ref={@widthInputFun} className="dont-bubble-keydown" />
+                Height:
+                <input type="text" defaultValue={@props.height} onKeyDown={@onKeyDown} ref={@heightInputFun} className="dont-bubble-keydown" />
+                <input type="button" className="lt-button" value="Submit" onClick={@onSubmit} />
+

+        else
+

+                Width:
+                {@props.width}
+                Height:
+                {@props.height}
+                <input type="button" className="lt-button" value="Edit" onClick={@onClickEdit} />
+

+
+    widthInputFun: (elem) ->
+        if elem? and (not @widthInput?)
+            # when creating this field, focus on it
+            node = React.findDOMNode(elem)
+
+            # code to focus on the input element 'node' at the END
+            # from http://stackoverflow.com/questions/1056359/set-mouse-focus-and-move-cursor-to-end-of-input-using-jquery
+            node.focus()
+            if node.setSelectionRange
+                len = $(node).val().length * 2
+                node.setSelectionRange(len, len)
+            else
+                $(node).val($(node).val())
+
+        @widthInput = elem
+
+
+    heightInputFun: (elem) ->
+        @heightInput = elem
+
+    onClickEdit: () ->
+        @setState
+            isEditing: true
+
+    onSubmit: () ->
+        widthStr = React.findDOMNode(@widthInput).value
+        heightStr = React.findDOMNode(@heightInput).value
+        if (not Utils.isValidInteger(widthStr)) or (not Utils.isValidInteger(heightStr))
+            return
+        width = parseInt(widthStr, 10)
+        height = parseInt(heightStr, 10)
+        if width <= 0 or height <= 0 or width >= 200 or height >= 200
+            return
+        @props.onSet(width, height)
+        @setState
+            isEditing: false
+
+    onCancelEdit: () ->
+        @setState
+            isEditing: false
+
+    onKeyDown: (event) ->
+        if event.which == 13 # enter
+            @onSubmit()
+            event.preventDefault()
+        else if event.which == 27 # escape
+            @onCancelEdit()
+            event.preventDefault()
+
 PuzzleGrid = React.createClass
     shouldComponentUpdate: (nextProps, nextState) -> not Utils.deepEquals(@props, nextProps)


Correct cleanup code for block blobs.

Correct cleanup code for block blobs.

--- src/coffee/dropship_list.coffee
+++ src/coffee/dropship_list.coffee
@@ -215,7 +215,7 @@
     callback()
     @

-  # The IndexedDB key for a file block.
+  # The IndexedDB key for a file's block.
   #
   # @param {DropshipFile} file the file that the block belongs to
   # @param {Number} blockId 0-based block sequence number
@@ -223,11 +223,17 @@
   #   "blobs" table
   fileBlockKey: (file, blockId) ->
     # Padding
-    stringId = blockId.toString(36)
+    stringId = blockId.toString 36
     while stringId.length < 8
       stringId = "0" + stringId

+    # - comes right before all valid fileUid symbols in ASCII.
     "#{file.uid}-#{stringId}"
+
+  # An upper bound for the IndexedDB keys for a file's blocks.
+  fileMaxBlockKey: (file) ->
+    # | comes after all blockId symbols in ASCII.
+    "#{file.uid}-|"

   # Retrieves a file's contents from the database.
   #
@@ -310,11 +316,20 @@
     @db (db) =>
       transaction = db.transaction 'blobs', 'readwrite'
       blobStore = transaction.objectStore 'blobs'
-      request = blobStore.delete file.uid
-      transaction.oncomplete = =>
-        callback false
-      transaction.onerror = (event) =>
-        @handleDbError event
+      keyRange = IDBKeyRange.bound @fileBlockKey(file, 0),
+                                   @fileMaxBlockKey(file)
+      cursor = blobStore.openCursor keyRange, 'next'
+      cursor.onsuccess = (event) =>
+        cursor = event.target.result
+        if cursor and cursor.key
+          request = cursor.delete()
+          request.onsuccess = (event) =>
+            cursor.continue()
+          request.onerror = (event) =>
+            callback true
+        else
+          callback false
+      cursor.onerror = (event) =>
         callback true

   # Removes the contents of files whose metadata is missing.


Another fix

Another fix

--- modules/image.coffee
+++ modules/image.coffee
@@ -5,31 +5,34 @@
 class ImageModule extends BotModule
   init: =>
     @registerCommand 'img', { allowDM: true }, (msg, args)=> Bing.images args, { top: 1 }, (error, res, body)=>
-      { results } = body.d
-      if results[0]
+      try
+        { results } = body.d
         msg.channel.uploadFile request(results[0].MediaUrl), @getImageName(results[0])

     @registerCommand 'rimg', { allowDM: true }, (msg, args)=> Bing.images args, { top: 50 }, (error, res, body)=>
-      { results } = body.d
-      chance = new Chance()
-      image = chance.pickone results
-      msg.channel.uploadFile request(image.MediaUrl), @getImageName(image)
+      try
+        { results } = body.d
+        chance = new Chance()
+        image = chance.pickone results
+        msg.channel.uploadFile request(image.MediaUrl), @getImageName(image)

     # NSFW (sorry, WET code)
     @registerCommand 'imgn', { allowDM: true }, (msg, args, d)=>
       return if not d.data.allowNSFW
       Bing.images args, { top: 1, adult: 'Off' }, (error, res, body)=>
-        { results } = body.d
-        msg.channel.uploadFile request(results[0].MediaUrl), @getImageName(results[0])
+        try
+          { results } = body.d
+          msg.channel.uploadFile request(results[0].MediaUrl), @getImageName(results[0])

     @registerCommand 'rimgn', { allowDM: true }, (msg, args, d)=>
       return if not d.data.allowNSFW
       Bing.images args, { top: 50, adult: 'Off' }, (error, res, body)=>
-        { results } = body.d
-        chance = new Chance()
-        image = chance.pickone results
-        return if not image
-        msg.channel.uploadFile request(image.MediaUrl), @getImageName(image)
+        try
+          { results } = body.d
+          chance = new Chance()
+          image = chance.pickone results
+          return if not image
+          msg.channel.uploadFile request(image.MediaUrl), @getImageName(image)

   getImageName: (image)=>
     ext = image.ContentType.split('/')[1]


Changes to schema docs display - Escape type names properly - Ignore metadata attributes in schemas

--- src/core/components/help.coffee
+++ src/core/components/help.coffee
@@ -160,7 +160,7 @@
     els = [
       mark 'API'
       h5 'List of Schemas'
-      ul (li "#{action (code schema.name), schema.name} #{variable schema.type}" for schema in schemas)
+      ul (li "#{action (code schema.name), schema.name} #{variable escape schema.type}" for schema in schemas)
     ]

     displayHtml Flow.HTML.render 'div', div els
@@ -170,12 +170,12 @@

     content = [
       mark 'Schema'
-      h5 "#{schema.name} (#{schema.type})"
+      h5 "#{schema.name} (#{escape schema.type})"
       h6 'Fields'
     ]

-    for field in schema.fields
-      content.push p "#{variable field.name}#{if field.required then '*' else ''} #{code field.type}
#{small field.help}"
+    for field in schema.fields when field.name isnt '__meta'
+      content.push p "#{variable field.name}#{if field.required then '*' else ''} #{code escape field.type}
#{small field.help}"

     displayHtml Flow.HTML.render 'div', div content


trying to dodge some of the focus issues on a Mac

--- client/editor.coffee
+++ client/editor.coffee
@@ -202,6 +202,7 @@

   open: (ee) ->
     @cm.setValue @oText = ee.text; @cm.clearHistory()
+    if D.mac then @cm.focus(); window.focus()
     # Constants for entityType:
     # DefinedFunction    1
     # SimpleCharArray    2


Add TODO for SHA optimization

Add TODO for SHA optimization

--- client/import.coffee
+++ client/import.coffee
@@ -44,6 +44,7 @@

       reader = new FileReader()
       reader.onload = ->
+        # TODO: Compute SHA in chunks
         hash = new Crypto.SHA256()
         hash.update @result
         sha256 = hash.finalize()


Link to issue on flickering fix

Link to issue on flickering fix

--- client/import.coffee
+++ client/import.coffee
@@ -150,6 +150,7 @@
   $(document).on 'dragenter', (e) ->
     e.preventDefault()

+    # For flickering issue: https://github.com/peerlibrary/peerlibrary/issues/203
     DRAGGING_OVER_DOM = true
     Meteor.setTimeout ->
       DRAGGING_OVER_DOM = false


Switch surface from computer to tablet so we can rotate it

--- framer/Components/DeviceComponent.coffee
+++ framer/Components/DeviceComponent.coffee
@@ -1323,7 +1323,7 @@
 	screenWidth: 2160
 	screenHeight: 1440
 	devicePixelRatio: 1.5
-	deviceType: "computer"
+	deviceType: "tablet"
 	minStudioVersion: iPhoneXReleaseVersion

 MicrosoftSurfacePro4 =
@@ -1333,7 +1333,7 @@
 	screenWidth: 2736
 	screenHeight: 1824
 	devicePixelRatio: 2
-	deviceType: "computer"
+	deviceType: "tablet"
 	minStudioVersion: iPhoneXReleaseVersion


Fix device orientation issues

Fix device orientation issues

--- framer/Components/DeviceComponent.coffee
+++ framer/Components/DeviceComponent.coffee
@@ -439,8 +439,8 @@
 					@showKeyboard(true)

 		else
-			@phone.properties = phoneProperties
-			@viewport.properties = contentProperties
+			@phone.props = phoneProperties
+			@viewport.props = contentProperties
 			@_update()

 			if _hadKeyboard


fix typo

fix typo

--- index.cjsx
+++ index.cjsx
@@ -505,10 +505,10 @@
             tmpShip = ""
             for i in [0..5]
               ship = mainFleet.ship[i]
-              if ship.hp.now < (0.2500001 * ship.hp.max) && ship.back == 1
+              if ship.hp.now < (0.2500001 * ship.hp.max) && ship.back == 0
                 tmpShip = tmpShip + ship.name + " "
               ship = escortFleet.ship[i]
-              if ship.hp.now < (0.2500001 * ship.hp.max) && ship.back == 1
+              if ship.hp.now < (0.2500001 * ship.hp.max) && ship.back == 0
                 tmpShip = tmpShip + ship.name + " "
             if tmpShip != "" and @state.enableProphetDamaged
               notify "#{tmpShip}" + __('Heavily damaged'),


Fixes #2737, fix fork() for both .js and .coffee, revised.

--- coffee-script.coffee
+++ coffee-script.coffee
@@ -6,23 +6,14 @@
 # If included on a webpage, it will automatically sniff out, compile, and
 # execute all scripts present in `text/coffeescript` tags.

-fs        = require 'fs'
-path      = require 'path'
-{Lexer}   = require './lexer'
-{parser}  = require './parser'
-helpers   = require './helpers'
-vm        = require 'vm'
-sourcemap = require './sourcemap'
-
-# Load and run a CoffeeScript file for Node, stripping any `BOM`s.
-loadFile = (module, filename) ->
-  raw = fs.readFileSync filename, 'utf8'
-  stripped = if raw.charCodeAt(0) is 0xFEFF then raw.substring 1 else raw
-  module._compile compile(stripped, {filename, literate: helpers.isLiterate filename}), filename
-
-if require.extensions
-  for ext in ['.coffee', '.litcoffee', '.md', '.coffee.md']
-    require.extensions[ext] = loadFile
+fs            = require 'fs'
+vm            = require 'vm'
+path          = require 'path'
+child_process = require 'child_process'
+{Lexer}       = require './lexer'
+{parser}      = require './parser'
+helpers       = require './helpers'
+sourcemap     = require './sourcemap'

 # The current CoffeeScript version number.
 exports.VERSION = '1.6.1'
@@ -154,6 +145,29 @@
   else
     vm.runInContext js, sandbox

+# Load and run a CoffeeScript file for Node, stripping any `BOM`s.
+loadFile = (module, filename) ->
+  raw = fs.readFileSync filename, 'utf8'
+  stripped = if raw.charCodeAt(0) is 0xFEFF then raw.substring 1 else raw
+  module._compile compile(stripped, {filename, literate: helpers.isLiterate filename}), filename
+
+# If the installed version of Node supports `require.extensions`, register
+# CoffeeScript as an extension.
+if require.extensions
+  for ext in ['.coffee', '.litcoffee', '.coffee.md']
+    require.extensions[ext] = loadFile
+
+# Patch `child_process.fork` so that Coffee scripts are able to fork both
+# CoffeeScript files, and JavaScript files, directly.
+{fork} = child_process
+child_process.fork = (path, args = [], options = {}) ->
+  execPath = if helpers.isCoffee(path) then 'coffee' else null
+  if not Array.isArray args
+    args = []
+    options = args or {}
+  options.execPath or= execPath
+  fork path, args, options
+
 # Instantiate a Lexer for our use here.
 lexer = new Lexer


removing CoffeeScript.activate() simply including the tag will do for text/coffeescript

--- coffee-script.coffee
+++ coffee-script.coffee
@@ -40,7 +40,6 @@

 # Activate CoffeeScript in the browser by having it compile and eval
 # all script tags with a content-type of text/coffeescript.
-exports.activate: ->
-  scripts: document.getElementsByTagName 'script'
-  for script in scripts when script.type is 'text/coffeescript'
-    eval exports.compile script.innerHTML
+if document? and document.getElementsByTagName
+  for tag in document.getElementsByTagName('script') when tag.type is 'text/coffeescript'
+    eval exports.compile tag.innerHTML


Listen to correct web port

Listen to correct web port

--- coffee/server.coffee
+++ coffee/server.coffee
@@ -31,7 +31,7 @@
 app.use(express.bodyParser())
 app.disable('x-powered-by')
 app.enable('verbose errors')
-app.set 'port', process.env.PORT or 8080
+app.set 'port', process.env.PORT_WWW or 8080
 app.use(app.router)

 # Error Handler


config: change test instances' client make target

--- config/main.dev.coffee
+++ config/main.dev.coffee
@@ -526,7 +526,7 @@

         echo '#---> BUILDING CLIENT <---#'
         scripts/install-npm.sh -d client -u
-        make -C #{projectRoot}/client dist
+        make -C #{projectRoot}/client unit-tests

         echo '#---> BUILDING GO WORKERS (@farslan) <---#'
         #{projectRoot}/go/build.sh


Make `findMarkers()` inclusive for screen rows queries

…as its counterpart in buffer coordinates. This is important because
the screen row could contain folds.

--- src/display-marker-layer.coffee
+++ src/display-marker-layer.coffee
@@ -308,14 +308,14 @@
           value = @displayLayer.translateScreenPosition(Point(value, 0)).row
         when 'endScreenRow'
           key = 'endRow'
-          value = @displayLayer.translateScreenPosition(Point(value, 0)).row
+          value = @displayLayer.translateScreenPosition(Point(value, Infinity), clipDirection: 'backward').row
         when 'intersectsBufferRowRange'
           key = 'intersectsRowRange'
         when 'intersectsScreenRowRange'
           key = 'intersectsRowRange'
           [startScreenRow, endScreenRow] = value
           startBufferRow = @displayLayer.translateScreenPosition(Point(startScreenRow, 0)).row
-          endBufferRow = @displayLayer.translateScreenPosition(Point(endScreenRow, 0)).row
+          endBufferRow = @displayLayer.translateScreenPosition(Point(endScreenRow, Infinity), clipDirection: 'backward').row
           value = [startBufferRow, endBufferRow]
         when 'containsBufferRange'
           key = 'containsRange'


js parser fixes to enable dropdown

js parser fixes to enable dropdown

--- src/javascript.coffee
+++ src/javascript.coffee
@@ -417,7 +417,7 @@
           if not known
             @jsSocketAndMark indentDepth, node.callee, depth + 1, NEVER_PAREN
           for argument, i in node.arguments
-            @jsSocketAndMark indentDepth, argument, depth + 1, NEVER_PAREN, known?.dropdown?[i]
+            @jsSocketAndMark indentDepth, argument, depth + 1, NEVER_PAREN, null, null, known?.fn?.dropdown?[i]
         when 'MemberExpression'
           @jsBlock node, depth, bounds
           @jsSocketAndMark indentDepth, node.object, depth + 1
@@ -497,7 +497,7 @@
         classes: @getClasses node
         socketLevel: @getSocketLevel node

-    jsSocketAndMark: (indentDepth, node, depth, precedence, bounds, classes) ->
+    jsSocketAndMark: (indentDepth, node, depth, precedence, bounds, classes, dropdown) ->
       unless node.type is 'BlockStatement'
         @addSocket
           bounds: bounds ? @getBounds node
@@ -505,6 +505,7 @@
           precedence: precedence
           classes: classes ? []
           accepts: @getAcceptsRule node
+          dropdown: dropdown

       @mark indentDepth, node, depth + 1, bounds


First pass at loading form values from URL parameters.

--- src/js/collection-editor.coffee
+++ src/js/collection-editor.coffee
@@ -154,9 +154,12 @@

 load_collection_form = ->
   collection = $('#collection_select').val()
-  if localStorage[collection]
-    for child in $('#collection_form').children()
-      if $(child).attr('id') && localStorage["#{collection}:#{$(child).attr('id')}"]?
+  for child in $('#collection_form').children()
+    if $(child).attr('id')?
+      if parse_query_string()[$(child).attr('id')]?
+        $(child).val(parse_query_string()[$(child).attr('id')])
+        # filter_url_params(parse_query_string(),[$(child).attr('id')])
+      else if localStorage["#{collection}:#{$(child).attr('id')}"]?
         if $(child).attr('class') == 'pagedown_container'
           $(child).find('.wmd-input').val(localStorage["#{collection}:#{$(child).attr('id')}"])
         else
@@ -315,8 +318,9 @@
 push_selected_collection = ->
   selected = $('#collection_select option:selected')[0]
   new_hash = "#collection=#{$(selected).attr('value')}"
+  filter_url_params(parse_query_string(),['collection'])
   new_url = if location.hash.length > 0
-    window.location.href.replace("#{location.hash}",new_hash)
+    window.location.href.replace("#{location.hash}","#{new_hash}&#{location.hash.substring(1)}")
   else
     window.location.href + new_hash
   history.pushState(null,$(selected).text(),new_url)


Can now `docpad install` multiple plugins at once

--- src/lib/docpad.coffee
+++ src/lib/docpad.coffee
@@ -3708,9 +3708,12 @@

 		# Install a plugin
 		if opts.plugin then tasks.addTask (complete) ->
-			opts.plugin = "docpad-plugin-#{opts.plugin}"  if /^docpad-plugin-/.test(opts.plugin) is false
-			opts.plugin += '@2'  if opts.plugin.indexOf('@') is -1
-			docpad.initNodeModule(opts.plugin, {
+			plugins =
+				for plugin in opts.plugin.split(/[,\s]+/)
+					plugin = "docpad-plugin-#{plugin}"  if plugin.indexOf('docpad-plugin-') isnt 0
+					plugin += '@2'  if plugin.indexOf('@') is -1
+					plugin
+			docpad.initNodeModule(plugins, {
 				output: true
 				next: complete
 			})


Special case Heroku https headers

Special case Heroku https headers

--- controllers/app.coffee
+++ controllers/app.coffee
@@ -1,9 +1,8 @@
 exports.forceHTTPS = (req, res, next) ->
-  # redirect to HTTPS in production
-  console.log req.secure
-  console.log process.env.NODE_ENV
-  if !req.secure && process.env.NODE_ENV == 'production'
-    console.log "https://#{req.headers.host}#{req.url}"
+  # redirect to HTTPS in production, take care of Heroku's special https
+  # handling
+  if not (req.secure or (req.header('x-forwarded-proto') == 'https')) and
+      process.env.NODE_ENV == 'production'
     res.redirect "https://#{req.headers.host}#{req.url}"
   else
     next()


Added instanceConfig to plugin base class

--- src/lib/plugin.coffee
+++ src/lib/plugin.coffee
@@ -19,6 +19,7 @@

 	# Plugin config
 	config: {}
+	instanceConfig: {}

 	# Plugin priority
 	priority: 500
@@ -31,7 +32,8 @@
 		@docpad = docpad

 		# Swap out our configuration
-		@config = balUtil.deepExtendPlainObjects({},@config)
+		@config = balUtil.deepClone(@config)
+		@instanceConfig = balUtil.deepClone(@instanceConfig)
 		@initialConfig = @config
 		@setConfig(config)

@@ -44,6 +46,14 @@
 		# Chain
 		@

+	# Set Instance Configuration
+	setInstanceConfig: (instanceConfig) ->
+		# Merge in the instance configurations
+		if instanceConfig
+			balUtil.safeDeepExtendPlainObjects(@instanceConfig, instanceConfig)
+			balUtil.safeDeepExtendPlainObjects(@config, instanceConfig)  if @config
+		@
+
 	# Set Configuration
 	setConfig: (instanceConfig=null) =>
 		# Prepare
@@ -51,8 +61,11 @@
 		userConfig = @docpad.config.plugins[@name]
 		@config = @docpad.config.plugins[@name] = {}

+		# Instance config
+		@setInstanceConfig(instanceConfig)  if instanceConfig
+
 		# Merge configurations
-		configPackages = [@initialConfig, userConfig, instanceConfig]
+		configPackages = [@initialConfig, userConfig, @instanceConfig]
 		configsToMerge = [@config]
 		docpad.mergeConfigurations(configPackages,configsToMerge)


Bugfix: Uncaught exception occurs when 'cd' takes none args. Use the root of the Project as default

--- lib/command-output-view.coffee
+++ lib/command-output-view.coffee
@@ -117,6 +117,7 @@
       @open()

   cd: (args)->
+    args = [atom.project.path] if not args[0]
     dir = resolve @getCwd(), args[0]
     fs.stat dir, (err, stat) =>
       if err


Proxy stopImmediatePropagation too

Proxy stopImmediatePropagation too

--- lib/rails/click_prepare.coffee
+++ lib/rails/click_prepare.coffee
@@ -28,6 +28,12 @@
       originalEvent.stopPropagation()
       stop.apply this

+    # Stopping immediate propgation stops the original click phase
+    stopImmediate = event.stopImmediatePropagation
+    event.stopImmediatePropagation = ->
+      originalEvent.stopImmediatePropagation()
+      stopImmediate.apply this
+
     originalEvent.target.dispatchEvent event
     event.result


removed unecessary action

removed unecessary action

--- lib/script-options-view.coffee
+++ lib/script-options-view.coffee
@@ -54,8 +54,6 @@

   close: ->
     atom.workspaceView.trigger "script:close-options"
-  save: ->
-    atom.workspaceView.trigger "script:save-options"
   run: ->
     atom.workspaceView.trigger "script:save-options"
     atom.workspaceView.trigger "script:close-options"


complain when selector matched nothing

complain when selector matched nothing


Handle empty lists

Handle empty lists

Related to #182

--- src/normalizer.coffee
+++ src/normalizer.coffee
@@ -99,7 +99,7 @@
           Utils.splitAncestors(curNode, lineNode.parentNode)
         if curNode.nextSibling?
           Utils.splitAncestors(curNode.nextSibling, lineNode.parentNode)
-        if !DOM.LIST_TAGS[curNode.tagName]?
+        if !DOM.LIST_TAGS[curNode.tagName]? or !curNode.firstChild
           DOM.unwrap(curNode)
           Normalizer.pullBlocks(lineNode)
         else


Add special item class

Add special item class

--- src/primitives.coffee
+++ src/primitives.coffee
@@ -116,7 +116,7 @@
     @content = convertToType val
     @

-class ObjectType extends Collection
+class Item extends Collection
   constructor: (val = {}, attributes) ->
     content = _.keys(val).map (name) -> new PropertyType name, val[name]
     super 'object', content, attributes
@@ -143,6 +143,8 @@
   keys: -> @content.map (val) -> val.attributes.name

   values: -> @content.map (val) -> val.get()
+
+class ObjectType extends Item

 # TODO: This needs to be a register so future types can be added
 convertToType = (val) ->


Bug fixed: settings/fbnotified overriding all user preferences

--- app/controllers/api/me.coffee
+++ app/controllers/api/me.coffee
@@ -124,7 +124,7 @@
 		fbNotifiable = false
 		if req.body.notifiable is 'on'
 			fbNotifiable = true
-		req.user.update { preferences: { fbNotifiable: fbNotifiable } },
+		req.user.update { 'preferences.fbNotifiable': fbNotifiable },
 		(err, user) ->
 			if err
 				req.logger.error("Erro??", err)