fix(menu): always auto-select the first item of the dropdown menu

--- src/components/menu.cjsx
+++ src/components/menu.cjsx
@@ -113,6 +113,9 @@

       @setState
         selectedIndex: newSelectionIndex
+    else
+      @setState
+        selectedIndex: 0

   componentWillUnmount: ->
     @subscriptions?.dispose()


Issue #1547 'use strict' octal literals prohibited

--- src/lexer.coffee
+++ src/lexer.coffee
@@ -134,6 +134,8 @@
     return 0 unless match = NUMBER.exec @chunk
     number = match[0]
     lexedLength = number.length
+    if octalLiteral = /^0[0-7]+$/.test number
+      @error "octal literals \"#{number}\" are not allowed"
     if binaryLiteral = /0b([01]+)/i.exec number
       number = (parseInt binaryLiteral[1], 2).toString()
     @token 'NUMBER', number


added test for gray

added test for gray

--- test/cmyk-test.coffee
+++ test/cmyk-test.coffee
@@ -2,13 +2,18 @@
 vows = require 'vows'
 assert = require 'assert'
 chroma = require '../chroma'
+
+round = (digits) ->
+    d = Math.pow 10,digits
+    return (v) ->
+        Math.round(v*d) / d

 vows
     .describe('Testing CMYK color conversions')
     .addBatch

         'export simple colors to CMYK':
-            topic: ['black','white','red','#0f0','blue','yellow','cyan','magenta']
+            topic: ['black','white','red','#0f0','blue','yellow','cyan','magenta','gray']
             'black':   (t) -> assert.deepEqual chroma(t[0]).cmyk(), [0,0,0,1]
             'white':   (t) -> assert.deepEqual chroma(t[1]).cmyk(), [0,0,0,0]
             'red':     (t) -> assert.deepEqual chroma(t[2]).cmyk(), [0,1,1,0]
@@ -17,6 +22,7 @@
             'yellow':  (t) -> assert.deepEqual chroma(t[5]).cmyk(), [0,0,1,0]
             'cyan':    (t) -> assert.deepEqual chroma(t[6]).cmyk(), [1,0,0,0]
             'magenta': (t) -> assert.deepEqual chroma(t[7]).cmyk(), [0,1,0,0]
+            'gray': (t) -> assert.deepEqual chroma(t[8]).cmyk().map(round(4)), [0,0,0,0.498]

         'parse simple CMYK colors':
             topic: [[0,0,0,1],[0,0,0,0],[0,1,1,0],[1,0,1,0],[1,1,0,0],[0,0,1,0],[1,0,0,0],[0,1,0,0]]


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/collision.coffee
+++ test/collision.coffee
@@ -47,7 +47,7 @@
   Collision.collide player, [enemy, enemy2], (p, e) ->
     ok(true)

-test "", ->
+test "Ray", ->

 module()


Use number of repositories which matched the repo regex for OCR repo count (see #4)

--- src/js/ogl-browse.coffee
+++ src/js/ogl-browse.coffee
@@ -78,8 +78,6 @@
   ocr_pattern = /[._]?201\d+-\d\d-\d\d-\d\d-\d\d[._]?/
   scan_pattern = /_.*$/
   repos = _.sortBy(repos, (repo) -> repo[repos_sort_by])
-  $('#repo-count').empty()
-  $('#repo-count').append($('<label>').text("#{repos.length} OCR repositories."))
   if repos_sort_reverse
     repos = repos.reverse()
   for repo in repos
@@ -106,6 +104,8 @@
         add_hathitrust_repo(repo_li_id, ocr_identifier, repo)
       else # archive.org
         add_archive_repo(repo_li_id, ocr_identifier, repo)
+  $('#repo-count').empty()
+  $('#repo-count').append($('<label>').text("#{$('#repo_list li').length} OCR repositories."))
   $('.sort-group button').attr('disabled',false)

 sort_button_click = () ->


Typo fix

Typo fix

--- test/diff_test.coffee
+++ test/diff_test.coffee
@@ -65,7 +65,7 @@
     it "should return undefined for two arrays with identical, empty array contents", ->
       assert.deepEqual undefined, diff([[]], [[]])

-    it "should return undefined for two arrays with identical, array contents including 'null'", ->
+    it "should return undefined for two arrays with identical array contents including 'null'", ->
       assert.deepEqual undefined, diff([1, null, null], [1, null, null])

     it "should return undefined for two arrays with identical, repeated contents", ->


:white_check_mark: Added more regex cases

--- spec/grammar-spec.coffee
+++ spec/grammar-spec.coffee
@@ -129,13 +129,20 @@

     it "works in a if", ->
       {tokens} = grammar.tokenizeLine("if (/ hello /i) {}")
-      expect(tokens[0]).toEqual value: "if", scopes: ["source.perl", "keyword.control.perl"]
       expect(tokens[1]).toEqual value: " (", scopes: ["source.perl"]
       expect(tokens[2]).toEqual value: "/", scopes: ["source.perl", "string.regexp.find.perl", "punctuation.definition.string.perl"]
       expect(tokens[3]).toEqual value: " hello ", scopes: ["source.perl", "string.regexp.find.perl"]
       expect(tokens[4]).toEqual value: "/", scopes: ["source.perl", "string.regexp.find.perl", "punctuation.definition.string.perl"]
       expect(tokens[5]).toEqual value: "i", scopes: ["source.perl", "string.regexp.find.perl", "punctuation.definition.string.perl", "keyword.control.regexp-option.perl"]
       expect(tokens[6]).toEqual value: ") {}", scopes: ["source.perl"]
+
+      {tokens} = grammar.tokenizeLine("if ($_ && / hello /i) {}")
+      expect(tokens[5]).toEqual value: " ", scopes: ["source.perl"]
+      expect(tokens[6]).toEqual value: "/", scopes: ["source.perl", "string.regexp.find.perl", "punctuation.definition.string.perl"]
+      expect(tokens[7]).toEqual value: " hello ", scopes: ["source.perl", "string.regexp.find.perl"]
+      expect(tokens[8]).toEqual value: "/", scopes: ["source.perl", "string.regexp.find.perl", "punctuation.definition.string.perl"]
+      expect(tokens[9]).toEqual value: "i", scopes: ["source.perl", "string.regexp.find.perl", "punctuation.definition.string.perl", "keyword.control.regexp-option.perl"]
+      expect(tokens[10]).toEqual value: ") {}", scopes: ["source.perl"]

   describe "when a regexp replace tokenizes", ->


Improve Atom configuration for latex

Improve Atom configuration for latex

Atom no longer removes trailing whitespace for latex files,
since other collaborators often leave them.

--- dotfiles/atom/.atom/config.cson
+++ dotfiles/atom/.atom/config.cson
@@ -74,18 +74,24 @@
 ".bibtex.text":
   editor:
     softWrapAtPreferredLineLength: false
-".gfm.source":
-  editor:
-    preferredLineLength: 80
+  whitespace:
+    removeTrailingWhitespace: false
 ".latex.log.text":
   editor:
     softWrapAtPreferredLineLength: false
 ".latex.tex.text":
   editor:
     softWrapAtPreferredLineLength: false
+  whitespace:
+    removeTrailingWhitespace: false
+".tex.text":
+  editor:
+    softWrapAtPreferredLineLength: false
+  whitespace:
+    removeTrailingWhitespace: false
+".gfm.source":
+  editor:
+    preferredLineLength: 80
 ".md.text":
   editor:
     preferredLineLength: 80
-".tex.text":
-  editor:
-    softWrapAtPreferredLineLength: false


Process queue after adding to it

Process queue after adding to it

--- src/lib/Component.coffee
+++ src/lib/Component.coffee
@@ -183,7 +183,8 @@
     # Component fired
     if @isOrdered()
       # Ordered mode. Instead of sending directly, we're queueing
-      @outputQ.push result if @isOrdered()
+      @outputQ.push result
+      do @processOutputQueue
     return

   addBracketForwards: (result) ->


fix function definition

fix function definition


Explicit type testing

Explicit type testing

--- test/spec/node.coffee
+++ test/spec/node.coffee
@@ -9,7 +9,10 @@
             describe '@_d3', ->
                 it "should initialize as an object", ->
                     testNode = alchemy._nodes[0]
-                    typeof testNode._d3.should.equal typeof Object()
+                    _d3Type = typeof testNode._d3
+                    objType = typeof {}
+
+                    typeof _d3Type.should.equal objType

                 it "should contain properties from d3 calculations", ->
                     testNode = alchemy._nodes[0]
@@ -27,7 +30,10 @@
         describe "@edges", ->
             it "should be an array", ->
                 testNode = alchemy._nodes[0]
-                typeof testNode.edges.should.equal typeof Array()
+                edgesType = typeof testNode.edges
+                arrayType = typeof []
+
+                edgesType.should.equal arrayType

             # Not currently supported by edge constructor
             it "should contain ids of all connected edges", ->


cleaner List::bind and some List essentials

--- src/monad.coffee
+++ src/monad.coffee
@@ -40,12 +40,44 @@
 # Haskell-style List with indeterminate binding!
 List = (items...) -> Trait.create items, (Trait.override (Trait {
   unit : List
-  bind : (fn) ->
-    @unit (Array.prototype.concat (
-        items.map (item) =>
-          (fn.call this, item).valueOf()
-      )... ) })
+  bind : (fn) -> concatMap fn, this })
   , (TMonad items))
+
+# Basic functions
+append = (left, right) -> List left..., right...
+
+head = (list) -> list.valueOf()[0]
+
+last = (list) -> list.valueOf()[list.length-1]
+
+tail = (list) -> List list[1..list.length]...
+
+init = (list) -> List list[1..list.length-1]...
+
+length = (list) -> list.length
+
+# List transformations
+
+map = (fn, list) -> List (list.map fn)...
+
+reverse = (list) ->
+  list = list.valueOf()
+  list.reverse()
+  List list...
+
+intersperse = (element, list) ->
+  List (list.valueOf().reduce ((acc, item) -> (Array acc..., element, item)))...
+
+intercalate = (list, lists) ->
+  (concat (intersperse list, lists).valueOf()...)
+
+# Special folds
+
+concat = (lists...) ->
+  List (Array.prototype.concat.call [], (list.valueOf() for list in lists)...)...
+
+concatMap = (fn, list) ->
+  concat (map fn, list).valueOf()...

 exports.TMonad = TMonad
 exports.Just = Just


fix typescript support

fix typescript support

--- src/beautifiers/typescript-formatter.coffee
+++ src/beautifiers/typescript-formatter.coffee
@@ -10,13 +10,13 @@
     beautify: (text, language, options) ->
         return new @Promise((resolve, reject) ->

-            TF = require("typescript-formatter/typescript-toolbox/lib/formatter")
-            opts = TF.createDefaultFormatCodeOptions()
+            format = require("typescript-formatter/lib/formatter")

+            opts = {}
             opts.TabSize = options.tab_width
             opts.IndentSize = options.indent_size

-            result = TF.applyFormatterToContent(text, opts)
+            result = format(text, opts)
             resolve result

         )


namespaced unbind

namespaced unbind

--- src/bindable.coffee
+++ src/bindable.coffee
@@ -83,12 +83,12 @@
       eventCallbacks[event] ||= []

       if namespace
+
       else
-
-      if callback
-        eventCallbacks[event].remove(callback)
-      else
-        eventCallbacks[event] = []
+        if callback
+          eventCallbacks[event].remove(callback)
+        else
+          eventCallbacks[event] = []
     else if namespace
       ;


Fix signup password confirmation

Fix signup password confirmation

--- app/assets/javascripts/darkswarm/controllers/authentication/signup_controller.js.coffee
+++ app/assets/javascripts/darkswarm/controllers/authentication/signup_controller.js.coffee
@@ -1,5 +1,7 @@
 Darkswarm.controller "SignupCtrl", ($scope, $http, $window, $location, Redirections, AuthenticationService) ->
   $scope.path = "/signup"
+
+  $scope.spree_user.password_confirmation = ''

   $scope.errors =
     email: null


More layer specs

More layer specs

--- spec_app/spec/javascripts/up/layer_spec.js.coffee
+++ spec_app/spec/javascripts/up/layer_spec.js.coffee
@@ -697,15 +697,26 @@

       describe 'for an element', ->

-        it "returns the element's layer"
+        it "returns the element's layer", (done) ->
+          makeLayers(3).then ->
+            expect(up.layer.get(up.layer.get(1).element)).toBe up.layer.get(1)
+            done()

       describe 'for an up.Layer', ->

-        it 'returns the given layer'
+        it 'returns the given layer', (done) ->
+          makeLayers(3).then ->
+            layer = up.layer.get(1)
+            expect(layer).toEqual jasmine.any(up.Layer)
+            expect(up.layer.get(layer)).toBe(layer)
+            done()

       describe 'for a layer name like "parent"', ->

-        it 'returns the layer matching that name'
+        it 'returns the layer matching that name', (done) ->
+          makeLayers(2).then ->
+            expect(up.layer.get('parent')).toBe(up.layer.root)
+            done()

       describe 'for a number', ->

@@ -759,7 +770,9 @@

         it 'returns an array of the given up.Layer', (done) ->
           makeLayers(3).then ->
-            expect(up.layer.getAll(up.layer.get(1))).toEqual [up.layer.get(1)]
+            layer = up.layer.get(1)
+            expect(layer).toEqual jasmine.any(up.Layer)
+            expect(up.layer.getAll(layer)).toEqual [layer]
             done()

       describe 'for "new"', ->


Fix a spec test

Fix a spec test

--- specs/unit/type/seq_type/default_name_spec.coffee
+++ specs/unit/type/seq_type/default_name_spec.coffee
@@ -4,6 +4,6 @@

 describe "SeqType#defaultName", ->

-  type = new SeqType(intType, "foo")
-  subject = type.defaultName()
-  subject.should.equal('[intType]')
+  it 'returns the expected', ->
+    type = new SeqType(intType, "foo")
+    should(type.defaultName()).equal('[intType]')


Reviewed documentation for connect-assets integration (#53)

--- examples/connect-assets.coffee
+++ examples/connect-assets.coffee
@@ -1,6 +1,47 @@
-# connect-assets example
+# connect-assets examples
+# =======================

-# First version, using views/assets.coffee
+# Overview
+# --------
+
+# The simplest usage pattern for connect-assets would be:
+#
+#   assets = require 'connect-assets'
+#   @use assets()
+#
+# However the defaults for connect-assets are different from
+# those used by Express and Zappa: for example the assets files
+# connect-assets are looked up in directory `./assets/` instead
+# of `./public/` in Express views.
+# The following two examples show how to use `connect-assets` in
+# a way consistent with Express and Zappa conventions.
+#
+# See also https://github.com/TrevorBurnham/connect-assets
+# and the example in the ./more_assets/ directory.
+
+# How this works
+# --------------
+
+# In the following two examples, the view contains code similar to:
+#
+#   html ->
+#     head ->
+#       text js 'app'
+#
+# The source code for `app` is found in ./public/js/app.coffe and
+# will in turn include ./public/js/dep.coffee and the content of
+# the ./public/js/vendors/ subtree. The `js` function is provided
+# by `connect-assets`.
+
+# First example
+# -------------
+
+# This first version uses the file ./views/assets.coffee as the
+# source for the view.
+#
+# The drawback of this solution is that functions `css`, `img`, and
+# `js` are added to the global scope, which is generally considered
+# bad coding practices.

 require('./zappajs') 3000, ->

@@ -10,12 +51,16 @@
     build: true
     buildDir: 'public/bin'
     minifyBuilds: false
-    # `css`, `img`, and `js` are added to the global scope.

   @get '/', ->
     @render 'assets', layout:no

-# Second version, using @view and no pollution of the global scope.
+# Second example
+# --------------
+
+# This second version uses ZappaJS' `@view` and injects the `css`,
+# `img`, and `js` functions via the `render_context` object, which
+# prevents pollution of the global scope.

 require('./zappajs') 3001, ->

@@ -41,6 +86,3 @@
   @get '/': ->
     # Note: this does not work with `hardcode` (Coffee[CK]up limitation).
     @render 'index', render_context
-
-# See https://github.com/TrevorBurnham/connect-assets
-# and the examples in the more_assets/ directory.


show something when no mappings in a mode

--- graphics.coffee
+++ graphics.coffee
@@ -291,15 +291,22 @@
     @ctx.beginPath()
     @ctx.moveTo(0,0)
     @ctx.lineTo(ocs * 2, 0)
-    @ctx.lineTo(ocs * 2, ocs * len)
-    @ctx.lineTo(0, ocs * len)
+    if len == 0
+      @ctx.lineTo(ocs * 2, ocs)
+      @ctx.lineTo(0, ocs)
+    else
+      @ctx.lineTo(ocs * 2, ocs * len)
+      @ctx.lineTo(0, ocs * len)
     @ctx.closePath()
     @ctx.stroke()

     # vertical divider
     @ctx.beginPath()
     @ctx.moveTo(ocs, 0)
-    @ctx.lineTo(ocs, ocs * len)
+    if len == 0
+      @ctx.lineTo(ocs, ocs)
+    else
+      @ctx.lineTo(ocs, ocs * len)
     @ctx.stroke()

     # horizontal dividers


Don't actually keep this in hubot

Don't actually keep this in hubot

--- scripts/boomtown.coffee
+++ scripts/boomtown.coffee
@@ -1,34 +0,0 @@
-# Description
-#   Trys to crash Hubot on purpose
-#
-# Commands:
-#   hubot boom [other text]- try to crash Hubot
-#   hubot boom emit with msg - try to crash Hubot by emitting an error with a msg
-#   hubot boom emit without msg - try to crash Hubot by emitting an error without a msg
-
-boomError = (boom, string) ->
-  new Error "Trying to #{boom} because you told me to #{string}"
-
-module.exports = (robot) ->
-  robot.respond /(boo+m)(?: (emit with(?:out)? msg|timeout|throw))?/i, (msg) ->
-    boom = msg.match[1]
-    how = msg.match[2]
-
-    switch msg.match[1]
-      when 'emit with msg'
-        robot.emit 'error', boomError(boom, how), msg
-      when 'emit without msg'
-        robot.emit 'error', boomError(boom, how)
-      when 'timeout'
-        setTimeout (->
-          throw boomError(boom, how)
-        ), 0
-      else
-        throw boomError(boom, how)
-
-
-  robot.error (err, msg) ->
-    robot.logger.error "BOOM"
-
-    if msg?
-      msg.reply err.stack, "BOOM"


Fix cloudapp script

Fix cloudapp script

--- scripts/cloudapp.coffee
+++ scripts/cloudapp.coffee
@@ -1,3 +1,6 @@
+http://cl.ly/jkbz
+http://cl.ly/jkbz/Screen%20Shot%202017-03-28%20at%201.39.37%20PM.png
+
 # Description:
 #   Allow Hubot to show what's lurking behind a CloudApp link
 #
@@ -15,7 +18,7 @@
 #   lmarburger

 module.exports = (robot) ->
-  robot.hear /(https?:\/\/(cl.ly)\/image\/[A-Za-z0-9]+)(\/[^\/]+)?/i, (msg) ->
+  robot.hear /(https?:\/\/cl.ly\/[A-Za-z0-9]+)(\/[^\/]+)?/i, (msg) ->
     return if msg.match[2]  # Ignore already embedded images.

     link = msg.match[1]


Test halmuruk.

Test halmuruk.

--- scripts/halmuruk.coffee
+++ scripts/halmuruk.coffee
@@ -22,8 +22,7 @@

 showMuRuk = (msg) ->
   imageMe msg, '할무룩', (url) ->
-    msg.send 'Hal 무룩...'
-    msg.send url
+    msg.send 'Hal 무룩...', url

 # below is copied from hubot-google-images


Run all gulp shell command as single statement.

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -33,9 +33,7 @@
   .pipe $.replace(noDevStrings[2], devStrings[2])
   .pipe gulp.dest(homePath)
   .pipe $.shell([
-    pluginInstall
-    pluginClean
-    pluginUpdate
+    [pluginInstall, pluginClean, pluginUpdate].join('; ')
   ])

 gulp.task 'nodev', ->
@@ -44,9 +42,6 @@
   .pipe $.replace(devStrings[1], noDevStrings[1])
   .pipe $.replace(devStrings[2], noDevStrings[2])
   .pipe gulp.dest(homePath)
-  .pipe $.shell([pluginInit])
   .pipe $.shell([
-    pluginInstall
-    pluginClean
-    pluginUpdate
+    [pluginInit, pluginInstall, pluginInit, pluginClean, pluginUpdate].join('; ')
   ])


Remove unused gulpfile

Remove unused gulpfile

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -1,8 +0,0 @@
-gulp = require 'gulp'
-jade = require 'gulp-jade'
-
-
-gulp.task 'default', ->
-	gulp.src './index.jade'
-	.pipe jade()
-	.pipe gulp.dest '.'


Styles: no styles support - fixed -

Styles: no styles support - fixed -

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -55,7 +55,7 @@
 karmaAction   = 'watch'
 buildDocs     = checkParam argv.docs
 buildPlay     = checkParam argv.play
-theme         = if checkParam argv.theme then argv.theme else no
+theme         = checkParam argv.theme

 # Build Tasks
@@ -63,9 +63,11 @@

 gulp.task 'styles', ->

-  # FIXME: put real theme build support here
-  unless theme
-    STYLES_PATH = require './src/themes/reset.includes.coffee'
+  if theme
+    resetStyles = require './src/themes/reset.includes.coffee'
+    # FIXME: require & put real theme styl files array here
+    themeStyles = []
+    STYLES_PATH = themeStyles.concat resetStyles

   gulp.src STYLES_PATH
     .pipe stylus()


update gulpfile to refresh with nested html files

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -57,7 +57,7 @@

 gulp.task 'html', ->
   gulp
-    .src('./example/*.html')
+    .src('./example/**/*.html')
     .pipe(connect.reload())

 gulp.task 'browserify', ->
@@ -86,10 +86,10 @@
       errLogToConsole: false
       onError: (e) -> log e.message
     }))
-    # .pipe(autoprefixer({
-    #   browsers: ['last 2 versions', 'Firefox >= 26', 'Explorer >= 8']
-    #   cascade: false
-    # }))
+    .pipe(autoprefixer({
+      browsers: ['last 2 versions', 'Firefox >= 26', 'Explorer >= 8']
+      cascade: false
+    }))
     .pipe(gulp.dest(sassBuildDir))

 gulp.task 'refresh', (callback) ->


refactor(stats): use --serve switch to open stats server

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -95,6 +95,7 @@
 isProd         = env.prod? or env.production?
 isWindows      = /^win/.test(process.platform)
 manifest       = {}
+serve          = env.serve?
 useBackendless = not (env.backend? or isProd)
 useSpecs       = useBackendless

@@ -811,20 +812,11 @@
 # Start a web server without rebuilding
 gulp.task 'serve', ->
 	startServer DIST_DIRECTORY
-
-# Start a web server for stats without rebuilding
-gulp.task 'serveStats', ->
-	startServer STATS_DIRECTORY
-	openApp STATS_DIRECTORY
+	openApp DIST_DIRECTORY

 # Start a web server
 gulp.task 'server', ['build'], ->
 	startServer DIST_DIRECTORY
-
-# Start a web server for stats
-gulp.task 'serverStats', ['stats'], ->
-	startServer STATS_DIRECTORY
-	openApp STATS_DIRECTORY

 # Process SPA
 gulp.task 'spa', ['scripts', 'styles'].concat(if isProd then 'templateCache' else 'views'), ->
@@ -861,7 +853,10 @@
 		.on 'error', onError

 # Execute stats
-gulp.task 'stats', ['plato']
+gulp.task 'stats', ['plato'], ->
+	if serve
+		startServer STATS_DIRECTORY
+		openApp STATS_DIRECTORY

 # Process styles
 gulp.task 'styles', ['less', 'css'], ->


continue build when there are CS errors

continue build when there are CS errors

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -55,6 +55,9 @@
     b.transform coffeeify
     b = b.bundle()
     b = b.pipe source entry[1]
+    .on 'error', ->
+      # report CoffeeScript errors in console and continue build
+      this.emit 'end'
     b = b.pipe buffer()

     # comment out this to turn off source maps


return spawn process exit code to track build errors

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -100,7 +100,7 @@

 # run tests in headless browser
 gulp.task 'test', ['build'], ->
-  spawn('./node_modules/mocha-phantomjs-core/phantomjs', [
+  phantom = spawn('./node_modules/mocha-phantomjs-core/phantomjs', [
     # allow headless browser to connect via HTTPS
     '--ignore-ssl-errors=yes'
     '--web-security=no'
@@ -111,6 +111,9 @@
     '{"useColors": true}'
   ], stdio: 'inherit')

+  phantom.on 'close', (code) ->
+    console.log 'child process exited with code ' + code
+
 # clear build directory
 gulp.task 'clean', ->
   gulp.src conf.build_dir, read: false


Fixed website watch tasks and website symlinks.

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -263,8 +263,8 @@
   server = liveServer.static(paths.build.website, options.liveServer.port)
   server.start()

-  # gulp.watch '#{BUILD_OUTPUT}/**/*.{css,js,png,svg}', (file) ->
-  #   server.notify.apply server, [ file ]
+  gulp.watch "#{paths.build.base}/**/*.{css,js,png,svg}", (file) ->
+    server.notify.apply server, [ file ]

 # Website Tasks ----------------------------------------------------------------

@@ -302,9 +302,11 @@
           console.log err
     .pipe gulp.dest(paths.build.website)

-  # TODO: Remove this when we can update everything load from 'styles' instead of 'css'
+  # Symlink Styles and Scripts
   gulp.src paths.build.styles
-    .pipe symlink "#{paths.build.website}/css", force: true
+    .pipe symlink("#{paths.build.website}/css", force: true)
+  gulp.src paths.build.scripts
+    .pipe symlink("#{paths.build.website}/scripts", force: true)

 gulp.task 'optimize-website', [ 'compile-website' ], ->
   # TODO: We probably don't need this.


Gulp social sharing example theme sass

Gulp social sharing example theme sass

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -44,7 +44,7 @@
       gulp.run 'uglify', ->

 gulp.task 'compass', ->
-  for path in ['', 'docs/welcome/']
+  for path in ['', 'docs/welcome/', 'docs/welcome/examples/social-sharing/']
     gulp.src("./#{ path }sass/*")
       .pipe(compass(
         sass: "#{ path }sass"


Fix index error

Fix index error

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -47,7 +47,7 @@
 	gulp.src "#{outline.src}/index.jade"
 		.pipe plumber()
 		.pipe jade(pretty: true)
-		.pipe htmlreplace(index)
+		.pipe htmlreplace(indexInject)
 		.pipe inject(gulp.src(bowerFiles(), read: false), {name: 'bower', addRootSlash: false, ignorePath: "/#{outline.dist}"})
 		.pipe gulp.dest("#{outline.dist}")


build task updated for circle ci

build task updated for circle ci

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -122,10 +122,8 @@
     gzip: true)

 gulp.task 'deploy', ['build'], () ->
-  remoteUrl = url.parse pkg.repository.url
-  # Add travis secure to remote url
   options =
-    remoteUrl: "#{remoteUrl.protocol}//#{process.env.GH_TOKEN}@#{remoteUrl.host}#{remoteUrl.pathname}"
+    remoteUrl: pkg.repository.url
   gulp.src './dist/**/*'
     .pipe ($.ghPages options)


Minify CSS Once More

Minify CSS Once More

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -40,7 +40,7 @@
       'last 2 Safari versions'
     ]
     .pipe concat 'qbn.css'
-    #.pipe minifyCss()
+    .pipe minifyCss()
     .pipe sourcemaps.write()
     .pipe gulp.dest "#{DEST}/styles"


zephyros 3.x changed the syntax, this fixes sdegutis/zephyros#13

--- tilde/zephyros.coffee
+++ tilde/zephyros.coffee
@@ -1,4 +1,4 @@
-# Based on this Config: https://github.com/sdegutis/zephyros/wiki/Sizeup-config
+# Based on this Config: https://github.com/sdegutis/zephyros/wiki.p-config
 # todo:
 # - snapback window
 # - send window space [dir]
@@ -8,43 +8,43 @@

 # Center Window
 bind "C", ["cmd", "ctrl"], -> moveWindow (win, frame) ->
-  frame.origin.x += frame.size.width / 6
-  frame.origin.y += frame.size.height / 8
-  frame.size.width /= 1.5
-  frame.size.height /= 1.3
+  frame.x += frame.w / 6
+  frame.y += frame.h / 8
+  frame.w /= 1.5
+  frame.h /= 1.3

 # Center Window with borders
 bind "b", ["cmd", "ctrl"], -> moveWindow (win, frame) ->
-  frame.origin.x += frame.size.width / 15
-  frame.size.width /= 1.15
+  frame.x += frame.w / 15
+  frame.w /= 1.15

 # Top Left Corner
 bind "1", ["cmd", "ctrl"], -> moveWindow (win, frame) ->
-  frame.size.width /= 1.5
-  frame.size.height /= 1.3
+  frame.w /= 1.5
+  frame.h /= 1.3

 # Top Right Corner
 bind "2", ["cmd", "ctrl"], -> moveWindow (win, frame) ->
-  width = win.screen().frameIncludingDockAndMenu().size.width
-  frame.origin.x = width - (frame.size.width / 1.5)
-  frame.size.width /= 1.5
-  frame.size.height /= 1.3
+  width = win.screen().frameIncludingDockAndMenu().w
+  frame.x = width - (frame.w / 1.5)
+  frame.w /= 1.5
+  frame.h /= 1.3

 # Bottom Left Corner
 bind "3", ["cmd", "ctrl"], -> moveWindow (win, frame) ->
-  height = win.screen().frameIncludingDockAndMenu().size.height
-  frame.origin.y = height - (frame.size.height / 1.3)
-  frame.size.width /= 1.5
-  frame.size.height /= 1.3
+  height = win.screen().frameIncludingDockAndMenu().h
+  frame.y = height - (frame.h / 1.3)
+  frame.w /= 1.5
+  frame.h /= 1.3

 # Bottom Right Corner
 bind "4", ["cmd", "ctrl"], -> moveWindow (win, frame) ->
-  height = win.screen().frameIncludingDockAndMenu().size.height
-  width = win.screen().frameIncludingDockAndMenu().size.width
-  frame.origin.x = width - (frame.size.width / 1.5)
-  frame.origin.y = height - (frame.size.height / 1.3)
-  frame.size.width /= 1.5
-  frame.size.height /= 1.3
+  height = win.screen().frameIncludingDockAndMenu().h
+  width = win.screen().frameIncludingDockAndMenu().w
+  frame.x = width - (frame.w / 1.5)
+  frame.y = height - (frame.h / 1.3)
+  frame.w /= 1.5
+  frame.h /= 1.3

 # show clipboard contents
 bind "i", ["cmd", "ctrl"], ->
@@ -52,45 +52,45 @@

 # Send Window Left
 bind "left", ["cmd", "ctrl"], -> moveWindow (win, frame) ->
-  frame.size.width /= 2
+  frame.w /= 2

 # Send Window Right
 bind "right", ["cmd", "ctrl"], -> moveWindow (win, frame) ->
-  frame.origin.x += frame.size.width / 2
-  frame.size.width /= 2
+  frame.x += frame.w / 2
+  frame.w /= 2

 # Send Window Up
 bind "up", ["cmd", "ctrl"], -> moveWindow (win, frame) ->
-  frame.size.height /= 2
+  frame.h /= 2

 # Send Window Down
 bind "down", ["cmd", "ctrl"], -> moveWindow (win, frame) ->
-  frame.origin.y += frame.size.height / 2
-  frame.size.height /= 2
+  frame.y += frame.h / 2
+  frame.h /= 2

 # Send Window Upper Left
 bind "left", ["shift", "alt", "ctrl"], -> moveWindow (win, frame) ->
-  frame.size.width /= 2
-  frame.size.height /= 2
+  frame.w /= 2
+  frame.h /= 2

 # Send Window Upper Right
 bind "up", ["shift", "alt", "ctrl"], -> moveWindow (win, frame) ->
-  frame.origin.y += frame.size.height / 2
-  frame.size.width /= 2
-  frame.size.height /= 2
+  frame.y += frame.h / 2
+  frame.w /= 2
+  frame.h /= 2

 # Send Window Lower Left
 bind "down", ["shift", "alt", "ctrl"], -> moveWindow (win, frame) ->
-  frame.origin.x += frame.size.width / 2
-  frame.size.width /= 2
-  frame.size.height /= 2
+  frame.x += frame.w / 2
+  frame.w /= 2
+  frame.h /= 2

 # Send Window Lower Right
 bind "right", ["shift", "alt", "ctrl"], -> moveWindow (win, frame) ->
-  frame.origin.x += frame.size.width / 2
-  frame.origin.y += frame.size.height / 2
-  frame.size.width /= 2
-  frame.size.height /= 2
+  frame.x += frame.w / 2
+  frame.y += frame.h / 2
+  frame.w /= 2
+  frame.h /= 2

 # Send Window to Next Display
 bind "right", ['ctrl', 'alt'], ->
@@ -109,13 +109,13 @@
   oldScreenRect = win.screen().frameWithoutDockOrMenu()
   newScreenRect = screen.frameWithoutDockOrMenu()

-  xRatio = newScreenRect.size.width  / oldScreenRect.size.width
-  yRatio = newScreenRect.size.height / oldScreenRect.size.height
+  xRatio = newScreenRect.w  / oldScreenRect.w
+  yRatio = newScreenRect.h / oldScreenRect.h

-  win.setFrame CGRectMake(Math.round((oldFrame.origin.x - NSMinX(oldScreenRect)) * xRatio) + NSMinX(newScreenRect),
-                          Math.round((oldFrame.origin.y - NSMinY(oldScreenRect)) * yRatio) + NSMinY(newScreenRect),
-                          Math.round(oldFrame.size.width * xRatio),
-                          Math.round(oldFrame.size.height * yRatio))
+  win.setFrame CGRectMake(Math.round((oldFrame.x - NSMinX(oldScreenRect)) * xRatio) + NSMinX(newScreenRect),
+                          Math.round((oldFrame.y - NSMinY(oldScreenRect)) * yRatio) + NSMinY(newScreenRect),
+                          Math.round(oldframe.w * xRatio),
+                          Math.round(oldframe.h * yRatio))

 moveWindow = (fn) ->
   win = api.focusedWindow()


cooldown finilazation

cooldown finilazation

--- src/cooldown.coffee
+++ src/cooldown.coffee
@@ -21,7 +21,7 @@

 player.I.shootTimer = 10 # => Pew! Pew!

-player.I
+player.I.update
 </pre></code>

 @name Cooldown


Revert "Updated changelog"

Revert "Updated changelog"

This reverts commit 0fd983695b8212e14e40c1e94fc48a66883ceb3e.

--- .annotations.cson
+++ .annotations.cson
@@ -2,11 +2,14 @@
   {
     lineNumber: 20
     title: "Text"
-    content: '''
-      text annotation2345
-
-    '''
+    content: "text annotation"
     type: "text"
+  }
+  {
+    lineNumber: 20
+    title: "HTML"
+    content: "HTML annotation"
+    type: "html"
   }
   {
     lineNumber: 20
@@ -16,17 +19,17 @@
   }
   {
     lineNumber: 20
-    title: "project relative file URL"
-    content: '''
-      file://$code-annotations/class_diagram.png
-
-    '''
+    title: "URL"
+    content: "https://atom.io/"
     type: "link"
   }
   {
     lineNumber: 20
-    title: "URL"
-    content: "https://atom.io/"
+    title: "project relative file URL"
+    content: '''
+      file://$code-annotations/class_diagram.png
+
+    '''
     type: "link"
   }
 ]


update atom config

update atom config

--- .atom/config.cson
+++ .atom/config.cson
@@ -29,6 +29,8 @@
     showErrorTabFile: false
     showErrorTabProject: false
     statusIconScope: "File"
+  "linter-rubocop":
+    command: "/usr/local/opt/rbenv/versions/2.3.0/bin/rubocop"
   "linter-ruby": {}
   "linter-write-good": {}
   "markdown-preview":
@@ -51,5 +53,3 @@
   whitespace:
     keepMarkdownLineBreakWhitespace: false
   wordcount: {}
-  "linter-rubocop":
-    executablePath: '/usr/local/opt/rbenv/versions/2.3.0/bin/rubocop'


update atom config

update atom config

--- .atom/config.cson
+++ .atom/config.cson
@@ -45,7 +45,8 @@
     enableNotice: true
     shellcheckExecutablePath: "/usr/local/bin/shellcheck"
     userParameters: "-x"
-  "linter-ui-default": {}
+  "linter-ui-default":
+    panelHeight: 99
   "linter-write-good": {}
   "markdown-preview":
     useGitHubStyle: true


atom: hide menu bar with alt

atom: hide menu bar with alt

--- .atom/config.cson
+++ .atom/config.cson
@@ -12,6 +12,7 @@
   "build-make":
     useMake: true
   core:
+    autoHideMenuBar: true
     disabledPackages: [
       "linter-python-pep257"
       "linter-pep257"


Only setting option values if they are provided

--- framer/Animators/Curves.coffee
+++ framer/Animators/Curves.coffee
@@ -5,7 +5,8 @@

 Bezier = (values...) ->
 	(options = {}) ->
-		options.values = values
+		if values.length > 0
+			options.values = values
 		new BezierCurveAnimator(options)

 # We define each in a seperate function here


Nice blue loading progress.

Nice blue loading progress.

--- framer/Extras/Preloader.coffee
+++ framer/Extras/Preloader.coffee
@@ -26,7 +26,7 @@
 				parent: @cover

 			@progressIndicator.railsColor = Color.grey(0, 0.1)
-			@progressIndicator.progressColor = Color.grey(0, 0.8)
+			@progressIndicator.progressColor = "rgb(75,169,248)"

 			@brand = new Layer
 				width: 96


チョコボットの反応を追加

チョコボットの反応を追加

--- scripts/reaction.coffee
+++ scripts/reaction.coffee
@@ -21,9 +21,10 @@
           "呼んだクエ？",
           "ナイスだクエ！",
           "リアクションありがとうクエ！",
-          "クエックエックエ~",
-          "クエックエックエ~チョコボール",
-          "-チョコボ-"
+          "クエックエックエ〜",
+          "クエックエックエ〜チョコボール",
+          "_チョコボ_",
+          "素晴らしいクポ！"
         ]
         text = arr[Math.floor(Math.random() * (arr.length + 1))]
         robot.send {room: message.item.channel}, text


Code clarity changes to xray.js

Code clarity changes to xray.js

--- lib/assets/javascripts/xray.js.coffee
+++ lib/assets/javascripts/xray.js.coffee
@@ -247,4 +247,3 @@
       width  : boxFrame.right - boxFrame.left
       height : boxFrame.bottom - boxFrame.top
     }
-


Add robustness to measuring tool (de)activation.

--- src/app.coffee
+++ src/app.coffee
@@ -309,12 +309,12 @@

         activateMeasuringTool: ->
             app.getRegion('map').currentView.turnOnMeasureTool()
-            $(app.getRegion('navigation').currentView.$el).one 'click', @deactivateMeasuringTool
-            $(app.getRegion('tourStart').currentView.$el).one 'click', @deactivateMeasuringTool
+            $(app.getRegion('navigation').currentView?.$el)?.one 'click', @deactivateMeasuringTool
+            $(app.getRegion('tourStart').currentView?.$el)?.one 'click', @deactivateMeasuringTool

         deactivateMeasuringTool: ->
             app.getRegion('map').currentView.turnOffMeasureTool()
-            $(app.getRegion('navigation').currentView.$el).off 'click', @deactivateMeasuringTool
+            $(app.getRegion('navigation').currentView?.$el)?.off 'click', @deactivateMeasuringTool
             if app.getRegion('tourStart').$el
                 $(app.getRegion('tourStart').currentView.$el).off 'click', @deactivateMeasuringTool


Scrolling reimplementation.

Scrolling reimplementation.

--- src/PPU.coffee
+++ src/PPU.coffee
@@ -47,13 +47,15 @@
         @objectAttributeMemory = (0 for [0..0x100]) # 256B

     resetRegisters: ->
-        @setControl 0       # 8-bit
-        @setMask 0          # 8-bit
-        @setStatus 0        # 8-bit
-        @oamAddress = 0     # 16-bit
-        @vramAddress = 0    # 16-bit
-        @vramReadBuffer = 0 # 8-bit
-        @fineXScroll = 0    # 3-bit
+        @setControl 0       #  8-bit
+        @setMask 0          #  8-bit
+        @setStatus 0        #  8-bit
+        @oamAddress = 0     # 15-bit
+        @vramReadBuffer = 0 #  8-bit
+        @vramAddress = 0    # 15-bit also known as 'Loopy_V'
+        @tempAddress = 0    # 15-bit also known as 'Loopy_T'
+        @writeToogle = 0    #  1-bit
+        @fineXScroll = 0    #  3-bit

     resetVariables: ->
         @scanline = -1 # Total 262+1 scanlines (-1..261)
@@ -65,16 +67,15 @@

     writeControl: (value) ->
         @setControl value
-        # Bits 0,1 (name table index) are copied into bits 10,11 of VRAM address register
-        @vramAddress = (@vramAddress & 0xF3FF) | (value & 0x03) << 10
+        @tempAddress = (@vramAddress & 0xF3FF) | (value & 0x03) << 10 # T[11,10] = C[1,0]
         value

     setControl: (value) ->
-        @bigAddressIncrement         = (value >>> 2) & 0x01 # Bit 2
-        @spritesPatternTableIndex    = (value >>> 3) & 0x01 # Bit 3
-        @backgroundPatternTableIndex = (value >>> 4) & 0x01 # Bit 4
-        @bigSprites                  = (value >>> 5) & 0x01 # Bit 5
-        @vblankGeneratesNMI          = (value >>> 7)        # Bit 7
+        @bigAddressIncrement         = (value >>> 2) & 0x01 # C[2]
+        @spritesPatternTableIndex    = (value >>> 3) & 0x01 # C[3]
+        @backgroundPatternTableIndex = (value >>> 4) & 0x01 # C[4]
+        @bigSprites                  = (value >>> 5) & 0x01 # C[5]
+        @vblankGeneratesNMI          = (value >>> 7)        # C[7]

     ###########################################################
     # Mask register
@@ -85,12 +86,12 @@
         value

     setMask: (value) ->
-        @monochromeMode            =  value        & 0x01  # Bit  0
-        @noSpriteClipping          = (value >>> 1) & 0x01  # Bit  1
-        @noBackgroundClipping      = (value >>> 2) & 0x01  # Bit  2
-        @backgroundVisible         = (value >>> 3) & 0x01  # Bit  3
-        @spritesVisible            = (value >>> 4) & 0x01  # Bit  4
-        @backgroundColorsIntensity = (value >>> 5)         # Bits 5-7
+        @monochromeMode            =  value        & 0x01  # M[0]
+        @noSpriteClipping          = (value >>> 1) & 0x01  # M[1]
+        @noBackgroundClipping      = (value >>> 2) & 0x01  # M[2]
+        @backgroundVisible         = (value >>> 3) & 0x01  # M[3]
+        @spritesVisible            = (value >>> 4) & 0x01  # M[4]
+        @backgroundColorsIntensity = (value >>> 5)         # M[7-5]

     ###########################################################
     # Status register
@@ -98,24 +99,21 @@

     readStatus: ->
         value = @getStatus()
-        # @clearFlagsAffectedByStatus()
-        value
-
-    clearFlagsAffectedByStatus: ->
         @vblankInProgress = 0
-        @vramAddress = 0
+        @writeToogle = 0
+        value

     getStatus: ->
-        @vramWritesIgnored     << 4 | # Bit 4
-        @spriteScalineOverflow << 5 | # Bit 5
-        @spriteZeroHit         << 6 | # Bit 6
-        @vblankInProgress      << 7   # Bit 7
+        @vramWritesIgnored     << 4 | # S[4]
+        @spriteScalineOverflow << 5 | # S[5]
+        @spriteZeroHit         << 6 | # S[6]
+        @vblankInProgress      << 7   # S[7]

     setStatus: (value) ->
-        @vramWritesIgnored         = (value >>> 4) & 0x01 # Bit 4
-        @spriteScalineOverflow     = (value >>> 5) & 0x01 # Bit 5
-        @spriteZeroHit             = (value >>> 6) & 0x01 # Bit 6
-        @vblankInProgress          = (value >>> 7)        # Bit 7
+        @vramWritesIgnored     = (value >>> 4) & 0x01 # S[4]
+        @spriteScalineOverflow = (value >>> 5) & 0x01 # S[5]
+        @spriteZeroHit         = (value >>> 6) & 0x01 # S[6]
+        @vblankInProgress      = (value >>> 7)        # S[7]

     ###########################################################
     # Object attribute memory access
@@ -125,25 +123,95 @@
         @oamAddress = address

     readOAMData: ->
-        @objectAttributeMemory[@oamAddress] # Does not increment the address
+        @objectAttributeMemory[@oamAddress] # Read does not increment the address.

     writeOAMData: (value) ->
         @objectAttributeMemory[@oamAddress] = value if not @isRenderingInProgress()
-        @oamAddress = (@oamAddress + 1) & 0xFF # Always increments the address
-        value
+        @oamAddress = (@oamAddress + 1) & 0xFF # Write always increments the address.
+        value
+
+    ###########################################################
+    # VRAM access
+    ###########################################################
+
+    writeAddress: (address) ->
+        @writeToogle = not @writeToogle
+        if @writeToogle # 1st write
+            addressHigh = (address & 0x3F) << 8
+            @tempAddress = (@tempAddress & 0x00FF) | addressHigh # High bits [13-8] (bit 14 is cleared)
+        else            # 2nd write
+            addressLow = address
+            @tempAddress = (@tempAddress & 0xFF00) | addressLow  # Low bits  [7-0]
+        address
+
+    readData: ->
+        address = @incrementAddress()
+        oldBufferContent = @vramReadBuffer
+        @vramReadBuffer = @read address                                         # Always increments the address
+        if @isPalleteAddress address then @vramReadBuffer else oldBufferContent # Delayed read outside the pallete memory area
+
+    writeData: (value) ->
+        address = @incrementAddress()                                   # Always increments the address
+        @ppuMemory.write address, value if not @isRenderingInProgress() # Only during VBLANK or disabled rendering
+        value
+
+    incrementAddress: ->
+        previousAddress = @vramAddress
+        @vramAddress = (@vramAddress + @getAddressIncrement()) & 0xFFFF
+        previousAddress
+
+    getAddressIncrement: ->
+        if @bigAddressIncrement then 0x20 else 0x01
+
+
+    ###########################################################
+    # Internal VRAM access
+    ###########################################################
+
+    read: (address) ->
+        value = @ppuMemory.read address
+        if @monochromeMode and @isPalleteAddress address
+            @getMonochromeColor value
+        else
+            value
+
+    write: (address, value) ->
+        @ppuMemory.write address, value
+
+    isPalleteAddress: (address) ->
+        (address & 0x3F00) == 0x3F00
+
+    getMonochromeColor: (index) ->
+        index & 0x30

     ###########################################################
     # Scrolling
     ###########################################################

+    # Uses the same register as VRAM addressing.
+    # The register has the following structure: $0yyy.NNYY.YYYX.XXXX
+    #   yyy = fine Y scroll (y position of active row of rendered tile)
+    #    NN = index of active name table
+    # YYYYY = coarse Y scroll (y position of rendered tile in name table)
+    # XXXXX = coarse X scroll (x position of rendered tile in name table)
+
     writeScroll: (value) ->
-        # Uses the same register as VRAM addressing.
-        # The register has the following structure: $0yyy.NNYY.YYYX.XXXX
-        #   yyy = fine Y scroll (y position of active row of rendered tile)
-        #    NN = index of active name table
-        # YYYYY = coarse Y scroll (y position of rendered tile in name table)
-        # XXXXX = coarse X scroll (x position of rendered tile in name table)
-        @writeAddress value
+        @writeToogle = not @writeToogle
+        if @writeToogle # 1st write
+            @fineXScroll = value & 0x07
+            coarseXScroll = value >> 3
+            @tempAddress = (@tempAddress & 0xFFE0) | coarseXScroll
+        else            # 2nd write
+            fineYScroll = (value & 0x07) << 12
+            coarseYScroll = (value & 0xF1) << 2
+            @tempAddress = (@tempAddress & 0x18C0) | coarseYScroll | fineYScroll
+        value
+
+    copyHorizontalScrollBits: ->
+        @vramAddress = (@vramAddress & 0x7BE0) | (@tempAddress & 0x041F) # V[10,4-0] = T[10,4-0]
+
+    copyVerticalScrollBits: ->
+        @vramAddress = (@vramAddress & 0x041F) | (@tempAddress & 0x7BE0) # V[14-11,9-5] = T[14-11,9-5]

     incrementFineXScroll: ->
         if @fineXScroll is 7
@@ -176,73 +244,33 @@
             @vramAddress += 0x0020                #     coarseScrollY++

     ###########################################################
-    # VRAM access
-    ###########################################################
-
-    writeAddress: (address) ->
-        @vramAddress = (@vramAddress << 8 | address) & 0xFFFF
-        address
-
-    readData: ->
-        oldBufferContent = @vramReadBuffer
-        @vramReadBuffer = @read @incrementAddress()                             # Always increments the address
-        if @isPalleteAddress address then @vramReadBuffer else oldBufferContent # Delayed read outside the pallete memory area
-
-    writeData: (value) ->
-        address = @incrementAddress()                                   # Always increments the address
-        @ppuMemory.write address, value if not @isRenderingInProgress() # Only during VBLANK or disabled rendering
-        value
-
-    incrementAddress: ->
-        previousAddress = @vramAddress
-        @vramAddress = (@vramAddress + @getAddressIncrement()) & 0xFFFF
-        previousAddress
-
-    getAddressIncrement: ->
-        if @bigAddressIncrement then 0x20 else 0x01
-
-
-    ###########################################################
-    # Internal VRAM access
-    ###########################################################
-
-    read: (address) ->
-        value = @ppuMemory.read address
-        if @isPalleteAddress address and @monochromeMode
-            @getMonochromeColorIndex value
-        else
-            value
-
-    write: (address, value) ->
-        @ppuMemory.write address, value
-
-    isPalleteAddress: (address) ->
-        (address & 0x3F00) == 0x3F00
-
-    getMonochromeColorIndex: (index) ->
-        index & 0x30
-
-    ###########################################################
     # Rendering
     ###########################################################

     tick: ->
         @renderFramePixel() if @isRenderingScanlineCycle()
+        @updateScrolling() if @isRenderingEnabled()
         @incrementCycle()

     isRenderingScanlineCycle: ->
         0 <= @scanline <= 239 and 1 <= @cycle <= 256

+    isRenderingInProgress: ->
+        not @vblankInProgress and @isRenderingEnabled()
+
+    isRenderingEnabled: ->
+        @spritesVisible or @backgroundVisible
+
     renderFramePixel: ->
-        @firstPixelIsRendered = @scanline is 0 and @cycle is 1
-        @framebufferPosition = 0 if @firstPixelIsRendered
+        @frameStart = @scanline is 0 and @cycle is 1
+        @framebufferPosition = 0 if @frameStart
+        backgroundColor = @renderBackgroundPixel() if @backgroundVisible
         spriteColor = @renderSpritePixel() if @spritesVisible
-        backgroundColor = @renderBackgroundPixel() if @backgroundVisible
-        @setFramePixel backgroundColor or 0
+        @setFramePixel spriteColor or backgroundColor or 0 # TODO rendering priority

     renderBackgroundPixel: ->
         # Optimization - we compute this only when coarse scroll X was incremented.
-        if @fineXScroll is 0 or @firstPixelIsRendered
+        if @fineXScroll is 0 or @frameStart
             # VRAM address register following structure: $0yyy.NNYY.YYYX.XXXX
             #   yyy = fine Y scroll (y position of active row of rendered tile)
             #    NN = index of active name table
@@ -268,11 +296,11 @@
         colorBit1 =  (@colorBit1Row >>> inverseFineXScroll) & 0x01
         colorBit2 = ((@colorBit2Row >>> inverseFineXScroll) & 0x01) << 1
         # Optimization - we compute this only when coarse scroll X was incremented 4 times.
-        if (@vramAddress & 0x0003) is 0 or @firstPixelIsRendered
+        if (@vramAddress & 0x0003) is 0 or @frameStart
             # We compute pointer to attribute table as $2.NN11.11YY.Y.XXX
             # where YYY and XXX are 3 upper bits of YYYYY and XXXXX.
             # Each attribute applies to area of 4x4 tiles (that's why we removed 2 lower bits of YYYYY and XXXXX).
-            attributeTableAddress = 0x2000 | (@vramAddress & 0x0C00) | 0x03C0
+            attributeTableAddress = 0x23C0 | (@vramAddress & 0x0C00)
             attributeNumber = (@vramAddress >>> 4) & 0x38 | (@vramAddress >>> 2) & 0x07
             attributeAddress = attributeTableAddress + attributeNumber
             attribute = @read attributeAddress
@@ -281,9 +309,6 @@
             # We have to find in which subarea are we in. This is decided by bit 1 of YYYYY and XXXXX values.
             subareaNumber = (@vramAddress >>> 5) & 0x02 | (@vramAddress >>> 1) & 0x01
             @colorBits43 = ((attribute >>> subareaNumber) & 0x03) << 2
-        # Increment scroll coordinates only when background is rendered
-        @incrementFineYScroll() if @cycle is 256
-        @incrementFineXScroll()
         # This 4-bit color value is actualy index to background color palette,
         # so we have to read 1 of 16 entries from that palette.
         color = @colorBits43 | colorBit2 | colorBit1
@@ -299,6 +324,13 @@
         @framebuffer[@framebufferPosition++] = RGBA_COLORS[color++]
         @framebuffer[@framebufferPosition++] = RGBA_COLORS[color]
         @framebufferPosition++ # Alpha is always 0xFF
+
+    updateScrolling: ->
+        @incrementFineYScroll() if @cycle is 256
+        @incrementFineXScroll() if 1 <= @cycle <= 256 # Increments coarse X scroll each 8th fine X scroll increment
+        @incrementCoarseXScroll() if @cyle is 328 or @cycle is 336 # Extra coarse X scroll
+        @copyHorizontalScrollBits() if @cycle is 257
+        @copyVerticalScrollBits() if 280 <= @cycle <= 304

     incrementCycle: ->
         @cycle++
@@ -319,7 +351,4 @@
     isFrameAvailable: ->
         @frameAvailable

-    isRenderingInProgress: ->
-        not @vblankInProgress and (@spritesVisible or @backgroundVisible)
-
 module.exports = PPU


a temp solution for notification permission

add a button to status bar to ask permission if require

--- src/scripts/panel.coffee
+++ src/scripts/panel.coffee
@@ -10,9 +10,25 @@
     @nick = @query '#user-nick'
     @list = @query '#users-list'
     @conn = @query '#conn-status'
+    # check add permition button
+    @_check()
     @
   _text: (el, txt) ->
     el[if el.innerText? then 'innerText' else 'textContent'] = txt
+  _check: ->
+    return unless (api = window.webkitNotifications)?.checkPermission() > 0
+    li = document.createElement 'li'
+    btn = document.createElement 'input'
+    btn.type = 'button'
+    btn.value = 'Allow Desktop Notifications'
+    btn.onclick = ->
+      api.requestPermission =>
+        @parentNode.removeChild @
+    li.appendChild btn
+    @el.appendChild li
+    console.log 'check', li, @el
+    @
+  # end of check
   update: ->
     return @ unless @init
     # user nick
@@ -75,7 +91,7 @@
       else if e.keyCode is 13 # new line
         _resize()
       else
-
+        # todo: delay _resize
       return
     # end of fire_change
     # @on event: 'keydown', handler: _resize


Add !magic

Add !magic

--- scripts/triggers.coffee
+++ scripts/triggers.coffee
@@ -41,6 +41,7 @@
   '!testing'      : "lolnope"
   '!goal'         : "GOOOOOOOOOOOOOOOOOOOOOOAAAAAAAAAAAAAAAAALLLLLLLLLL!!!!!!!"
   '!unstoppable'  : "I am unstoppable!!! http://i.imgur.com/ALHS4Za.png"
+  '!magic'        : "http://www.reactiongifs.com/r/mgc.gif"

 module.exports = (robot) ->
   robot.hear /(([^:\s!]+)[:\s]+)?(!\w+)(.*)/i, (msg) ->


Fix tweet.coffee for v1.1 API

Fix tweet.coffee for v1.1 API

After the API retirement, this stopped working. This updates it to use
the ntwitter package, and follows the same configuration conventions as
tweet-mention.coffee for re-use.

It does depend on a fork of ntwitter though, and will need to be so
until AvianFlu/ntwitter#110 is fixed

--- src/scripts/tweet.coffee
+++ src/scripts/tweet.coffee
@@ -2,27 +2,68 @@
 #   Display a random tweet from twitter about a subject
 #
 # Dependencies:
-#   None
+#    "ntwitter" : "https://github.com/sebhildebrandt/ntwitter/tarball/master",
 #
 # Configuration:
-#   None
+#   HUBOT_TWITTER_CONSUMER_KEY
+#   HUBOT_TWITTER_CONSUMER_SECRET
+#   HUBOT_TWITTER_ACCESS_TOKEN_KEY
+#   HUBOT_TWITTER_ACCESS_TOKEN_SECRET
 #
 # Commands:
 #   hubot <keyword> tweet - Returns a link to a tweet about <keyword>
 #
+# Notes:
+#   There's an outstanding issue on AvianFlu/ntwitter#110 for search and the v1.1 API.
+#   sebhildebrandt is a fork that is working, so we recommend that for now. This
+#   can be removed after the issue is fixed and a new release cut, along with updating the dependency
+#
 # Author:
-#   atmos
+#   atmos, technicalpickles
+
+ntwitter = require 'ntwitter'
+inspect = require('util').inspect

 module.exports = (robot) ->
+  auth =
+    consumer_key: process.env.HUBOT_TWITTER_CONSUMER_KEY
+    consumer_secret: process.env.HUBOT_TWITTER_CONSUMER_SECRET
+    access_token_key: process.env.HUBOT_TWITTER_ACCESS_TOKEN_KEY
+    access_token_secret: process.env.HUBOT_TWITTER_ACCESS_TOKEN_SECRET
+
+  twit = undefined
+
   robot.respond /(.*) tweet/i, (msg) ->
-    search = escape(msg.match[1])
-    msg.http('http://search.twitter.com/search.json')
-      .query(q: search)
-      .get() (err, res, body) ->
-        tweets = JSON.parse(body)
+    unless auth.consumer_key
+      msg.send "Please set the HUBOT_TWITTER_CONSUMER_KEY environment variable."
+      return
+    unless auth.consumer_secret
+      msg.send "Please set the HUBOT_TWITTER_CONSUMER_SECRET environment variable."
+      return
+    unless auth.access_token_key
+      msg.send "Please set the HUBOT_TWITTER_ACCESS_TOKEN_KEY environment variable."
+      return
+    unless auth.access_token_secret
+      msg.send "Please set the HUBOT_TWITTER_ACCESS_TOKEN_SECRET environment variable."
+      return

-        if tweets.results? and tweets.results.length > 0
-          tweet  = msg.random tweets.results
-          msg.send "http://twitter.com/#!/#{tweet.from_user}/status/#{tweet.id_str}"
+
+    twit ?= new ntwitter auth
+
+
+    twit.verifyCredentials (err, data) ->
+      if err
+        msg.send "Encountered a problem verifing twitter credentials:(", inspect err
+        return
+
+      q = escape(msg.match[1])
+      twit.search q, (err, data) ->
+        if err
+          msg.send "Encountered a problem twitter searching :(", inspect err
+          return
+
+        if data.statuses? and data.statuses.length > 0
+          status = msg.random data.statuses
+          msg.send "http://twitter.com/#!/#{status.user.screen_name}/status/#{status.id_str}"
         else
           msg.reply "No one is tweeting about that."


Remove trailing whitespace

Remove trailing whitespace

--- src/scripts/tweet.coffee
+++ src/scripts/tweet.coffee
@@ -15,7 +15,7 @@
 #
 # Notes:
 #   There's an outstanding issue on AvianFlu/ntwitter#110 for search and the v1.1 API.
-#   sebhildebrandt is a fork that is working, so we recommend that for now. This
+#   sebhildebrandt is a fork that is working, so we recommend that for now. This
 #   can be removed after the issue is fixed and a new release cut, along with updating the dependency
 #
 # Author:


provide extended option

provide extended option

--- src/app.coffee
+++ src/app.coffee
@@ -102,7 +102,9 @@
 		wifi = Promise.promisifyAll(wifi)
 		console.log('WiFi initialized')

-		app.use(bodyParser.urlencoded())
+		app.use(bodyParser.urlencoded({
+		  extended: true
+		}))
 		app.use(bodyParser.json())

 		app.use(express.static(__dirname + '/public'))


Create path helper functions

Create path helper functions

Fixes #17

--- src/cli.coffee
+++ src/cli.coffee
@@ -40,6 +40,36 @@
     @args = @args.argv

   ###
+  Section: Helpers
+  ###
+
+  # Public: Gets the path to the directory where documentation should be stored.
+  #
+  # * `subpath` (optional) {String} to join to the docs directory.
+  #
+  # Returns a {String} containing the absolute path to the documentation directory with optional
+  #   subpath.
+  docsDirectory: (subpath) ->
+    @docsDir ?= path.resolve('./docs')
+    if subpath
+      path.join(@docsDir, subpath)
+    else
+      @docsDir
+
+  # Public: Gets the path to the root Endokken code directory.
+  #
+  # * `subpath` (optional) {String} to join to the source directory.
+  #
+  # Returns a {String} containing the absolute path to the root Endokken code directory with
+  #   optional subpath.
+  sourceDirectory: (subpath) ->
+    @sourceDir ?= path.dirname(path.resolve(__dirname))
+    if subpath
+      path.join(@sourceDir, subpath)
+    else
+      @sourceDir
+
+  ###
   Section: Metadata
   ###

@@ -64,11 +94,11 @@
   ###

   buildDocsDirectory: ->
-    fs.mkdirSync('./docs') unless fs.existsSync('./docs')
+    fs.mkdirSync(@docsDirectory()) unless fs.existsSync(@docsDirectory())

-    staticPath = path.join(__dirname, '../static')
+    staticPath = @sourceDirectory('static')
     for source in fs.readdirSync(staticPath)
-      @copyFile(path.join(staticPath, source.toString()), './docs')
+      @copyFile(path.join(staticPath, source.toString()), @docsDirectory())

   copyFile: (source, dir) ->
     destination = path.join(dir, path.basename(source))
@@ -109,9 +139,9 @@
     fs.writeFileSync(filePath, doc)

   renderFile: (file) ->
-    @render(FilePage.render(file), "./docs/#{path.basename(file, path.extname(file))}")
+    @render(FilePage.render(file), @docsDirectory(path.basename(file, path.extname(file))))

   renderClass: (klass) ->
-    @render(ClassPage.render(klass), "./docs/#{klass.name}")
+    @render(ClassPage.render(klass), @docsDirectory(klass.name))

 module.exports = Cli


Added ability to get fans and fan ofs from a user.

Added ability to get fans and fan ofs from a user using a users id

--- src/bot.coffee
+++ src/bot.coffee
@@ -619,13 +619,31 @@
         callback {ids: res, success: true}

-  getFanOf: (callback) ->
+  getFanOf: ->
     rq = api: 'user.get_fan_of'
-    @_send rq, callback
-
-
-  getFans: (callback) ->
+    callback = null
+    if arguments.length == 1
+      if typeof arguments[0] == 'function'
+        callback = arguments[0]
+      else if typeof arguments[0] == 'string'
+        rq.userid = arguments[0]
+    else if arguments.length == 2
+      rq.userid = arguments[0]
+      callback  = arguments[1]
+    @_send rq, callback
+
+
+  getFans: ->
     rq = api: 'user.get_fans'
+    callback = null
+    if arguments.length == 1
+      if typeof arguments[0] == 'function'
+        callback = arguments[0]
+      else if typeof arguments[0] == 'string'
+        rq.userid = arguments[0]
+    else if arguments.length == 2
+      rq.userid = arguments[0]
+      callback  = arguments[1]
     @_send rq, callback


Back side should be visible

Back side should be visible

--- src/css.coffee
+++ src/css.coffee
@@ -19,6 +19,5 @@
 	box-sizing: border-box;
 	-webkit-box-sizing: border-box;
 	-webkit-transform-origin: 50% 50% 0%;
-	-webkit-backface-visibility: hidden;
 	-webkit-transform-style: flat;
 }"


fix https://github.com/sagemathinc/smc/issues/236: "Arrows in 3-D vector fields do not have heads"

--- src/smc-webapp/3d.coffee
+++ src/smc-webapp/3d.coffee
@@ -88,6 +88,7 @@

         @init_eval_note()
         opts.cb?(undefined, @)
+        # window.w = @   # for debugging

     # client code should call this when start adding objects to the scene
     init: () =>
@@ -355,14 +356,24 @@
             points     : required
             thickness  : 1
             color      : "#000000"
-            arrow_head : false  # TODO
+            arrow_head : false
         @show_canvas()

-        geometry = new THREE.Geometry()
-        for a in o.points
-            geometry.vertices.push(@vector(a))
-        line = new THREE.Line(geometry, new THREE.LineBasicMaterial(color:o.color, linewidth:o.thickness))
-        @scene.add(line)
+        if o.arrow_head
+            # Draw an arrowhead using the ArrowHelper: https://github.com/mrdoob/three.js/blob/master/src/extras/helpers/ArrowHelper.js
+            orig = @vector(o.points[0])
+            p1   = @vector(o.points[1])
+            dir  = new THREE.Vector3(); dir.subVectors(p1, orig)
+            length = dir.length()
+            dir.normalize()
+            @scene.add(new THREE.ArrowHelper(dir, orig, length, o.color))
+
+        else
+
+            geometry = new THREE.Geometry()
+            for a in o.points
+                geometry.vertices.push(@vector(a))
+            @scene.add(new THREE.Line(geometry, new THREE.LineBasicMaterial(color:o.color, linewidth:o.thickness)))

     add_point: (opts) =>
         o = defaults opts,
@@ -437,7 +448,7 @@
         else
             has_local_colors = false

-
+
         vertices = myobj.vertex_geometry
         for objects in [0...myobj.face_geometry.length]
             #console.log("object=", misc.to_json(myobj))


Support path-based grammar detection when using stdin

--- src/cli.coffee
+++ src/cli.coffee
@@ -9,6 +9,7 @@
                 .describe('o', 'File path to write the HTML output to').alias('o', 'output').string('o')
                 .describe('s', 'Scope name of the grammar to use').alias('s', 'scope').string('s')
                 .describe('v', 'Output the version').alias('v', 'version').boolean('v')
+                .describe('f', 'File path to use for grammar detection when reading from stdin').alias('f', 'file-path').string('f')
   optimist.usage """
     Usage: highlights [options] [file]

@@ -46,12 +47,13 @@
     else
       console.log(html)
   else
+    filePath = cli.argv.f
     process.stdin.resume()
     process.stdin.setEncoding('utf8')
     fileContents = ''
     process.stdin.on 'data', (chunk) -> fileContents += chunk.toString()
     process.stdin.on 'end', ->
-      html = new Highlights().highlightSync({fileContents, scopeName: cli.argv.scope})
+      html = new Highlights().highlightSync({filePath, fileContents, scopeName: cli.argv.scope})
       if outputPath
         fs.writeFileSync(outputPath, html)
       else


This only worked because dirs is a reference to argv._. Use dirs consistently and make clear in which case we fall back to the default

--- src/cli.coffee
+++ src/cli.coffee
@@ -42,10 +42,12 @@

   preview: (argv) ->
     preview = require('./preview')
-    dirs = argv._
-    dirs.push process.cwd() unless dirs.length
+    if argv._.length == 0
+      dirs = [process.cwd()]
+    else
+      dirs = argv._

-    preview argv._, (err, cnt, port) ->
+    preview dirs, (err, cnt, port) ->
       if err then return console.log chalk.red(err)
       console.log chalk.green("\\\\\\  ///  Versal SDK preview is started")
       console.log chalk.yellow(" \\\\\\///   ") + chalk.white("open http://localhost:#{port} in your favorite browser")


Updated arg ordering

Updated arg ordering

--- src/cli.coffee
+++ src/cli.coffee
@@ -68,8 +68,8 @@
   # Configures the nconf mapping where the priority matches the order
   configure: =>
     @set_overrides()
+    @set_env()
     @set_argv()
-    @set_env()
     @set_file()
     @set_defaults()


Add new props for setting inline style

Add new props for setting inline style

--- src/index.cjsx
+++ src/index.cjsx
@@ -13,6 +13,20 @@
     onChange: (value) ->
     deleteButton: false
     spinnerOptions: {}
+    navTabStyle: {
+      display: 'inline-block'
+    }
+    tabStyle: {
+      listStyle: 'none'
+      float: 'left'
+      cursor: 'pointer'
+    }
+    textareaStyle: {
+      display: 'block'
+      resize: 'none'
+    }
+    buttonStyle: {}
+    deleteButtonStyle: {}

   getInitialState: ->
     state = {
@@ -33,19 +47,6 @@
       'react-markdown-textarea__nav__item--active': @state.active is "preview"
     })

-    ulStyles = {
-      display: 'inline-block'
-    }
-    liStyles = {
-      listStyle: 'none'
-      float: 'left'
-      cursor: 'pointer'
-    }
-    textareaStyles = {
-      display: 'block'
-      resize: 'none'
-    }
-
     # Are we writing or previewing?
     #
     # Swap between writing and previewing states.
@@ -56,7 +57,7 @@
         value={@state.value}
         onChange={@handleChange}
         ref="textarea"
-        style={textareaStyles}
+        style={@props.textareaStyles}
        />
     else
       textarea = <div
@@ -67,9 +68,9 @@
     # Add preview?
     unless @props.noPreview
       tabs =
-

-
Write

-
Preview

+

+
Write

+
Preview

     return (
@@ -79,12 +80,14 @@
           {textarea}
           <button
             onClick={@_onSave}
+            style={@props.buttonStyle}
             disabled={if @props.saving then "disabled" else false}
             className="react-markdown-textarea__save-button">
               {@props.buttonText}
           </button>
           { if @props.deleteButton
             <button
+              style={@props.deleteButtonStyle}
               onClick={@_onDelete}
               disabled={if @props.saving then "disabled" else false}
               className="react-markdown-textarea__delete-button">


Add room information when a user joins/leaves

--- src/irc.coffee
+++ src/irc.coffee
@@ -258,11 +258,13 @@
     bot.addListener 'join', (channel, who) ->
       console.log('%s has joined %s', who, channel)
       user = self.createUser channel, who
+      user.room = channel
       self.receive new EnterMessage(user)

     bot.addListener 'part', (channel, who, reason) ->
       console.log('%s has left %s: %s', who, channel, reason)
       user = self.createUser '', who
+      user.room = channel
       self.receive new LeaveMessage(user)

     bot.addListener 'kick', (channel, who, _by, reason) ->


minor fix

minor fix

--- src/map.coffee
+++ src/map.coffee
@@ -1,5 +1,5 @@
 class Map extends Meppit.BaseClass
-  MAXZOOM: 16
+  MAXZOOM: 15
   defaultOptions:
     element: document.createElement 'div'
     zoom: 14
@@ -127,7 +127,10 @@
   fit: (data) ->
     return this if not data?
     bounds = @_getBounds data
-    @leafletMap.fitBounds bounds, maxZoom: @MAXZOOM
+    @leafletMap.fitBounds bounds, {
+      maxZoom: @MAXZOOM,
+      animate: false
+    } if bounds?
     this

   panTo: (data) ->


mmd refactoring missed important return value

--- src/mmd.coffee
+++ src/mmd.coffee
@@ -18,6 +18,7 @@
       @children[name].plural on
     else
       @children[name] = new NodeType name
+    @children[name]

 analyze = (et, continuing) ->
   root = new NodeType 'root'


应用到禁鬼畜

应用到禁鬼畜

--- lib/module/publish-module-view.coffee
+++ lib/module/publish-module-view.coffee
@@ -382,6 +382,8 @@

   #  upload_module_use_to_application
   actModuleToApp:(e) ->
+    _btn = @.find("button[name=uploadMApp]")
+    _btn.attr("disabled",true)
     # alert "ssss"
     checkboxList = this.find('input[type=checkbox]')
     app_ids = []
@@ -447,9 +449,11 @@
                   alert "error"
               client.uploadModuleAndAct(params)
               util.removeFileDirectory(PathM.join zipPath,zipName)
+              _btn.attr("disabled",false)
           else
             util.removeFileDirectory(PathM.join zipPath,zipName)
             console.log "文件不存在#{$(btn2).val()}"
+            _btn.attr("disabled",false)
         iconPath = PathM.join @uploadBtn.val(),"..","icon.png"
         #当存在 icon 时 上传Icon后再上传模块信息
         #否则直接上床模块信息
@@ -468,6 +472,7 @@
               fs.exists(@uploadBtn.val(),configFilePathCallBack)
             error: =>
               # console.log iconPath
+              _btn.attr("disabled",false)
               console.log "上传icon失败"
               alert "上传icon失败"
           client.uploadFile(fileParams2,"module","")


Automatically pull out default export

Automatically pull out default export

--- src/use.coffee
+++ src/use.coffee
@@ -6,5 +6,9 @@
     path = resolve.sync pkg, basedir: process.cwd()
     pkg = require path

+    # Support CJS formatted ES modules with named + default exports
+    if pkg.default?
+      pkg = pkg.default
+
   if isFunction pkg
     pkg opts


Renamed view variable

Renamed view variable

--- example/todo/TodoAppView.coffee
+++ example/todo/TodoAppView.coffee
@@ -1,7 +1,7 @@

 define ['cs!tubularHtml', 'cs!tubularForm'], (tubularHtml, tubularForm) ->
   ->
-    this._ = window.app;
+    this.app = window.app;

     tubularHtml.install this, (element) ->
       # immediately append
@@ -32,7 +32,7 @@
       menu

     @element '.acme-container-box[role=container]', {
-      dataTodoItemCount: '{{ _.itemList.length }}'
+      dataTodoItemCount: '{{ app.itemList.length }}'
     }, ->

       @fork { tabs: createMenu([ 'Main', 'Settings' ]) }, ->
@@ -49,12 +49,12 @@
               @element 'legend', ->
                 @text '{{ tab.label }}'

-      @when '_.itemList.length', ->
+      @when 'app.itemList.length', ->
         @element 'h1#sampleHeading', ->
-          @text 'Hello, world {{ _.itemList.length }}'
+          @text 'Hello, world {{ app.itemList.length }}'

         @element 'ul', ->
-          @each '_.itemList', 'item', ->
+          @each 'app.itemList', 'item', ->
             @element 'li', ->
               @text 'This is: {{ item.label }}'

@@ -76,12 +76,12 @@

       @element 'a', { href: '#' }, ->
         @text 'Add Item'
-        @onClick => @_.addItem()
+        @onClick => @app.addItem()

       @element 'a', { href: '#' }, ->
         @text 'Update Item'
-        @onClick => @_.updateItem()
+        @onClick => @app.updateItem()

       @element 'a', { href: '#' }, ->
         @text 'Remove Item'
-        @onClick => @_.removeItem()
+        @onClick => @app.removeItem()


Refactored Set

Refactored Set

--- src/set.coffee
+++ src/set.coffee
@@ -1,3 +1,5 @@
+{inherit} = require './util'
+
 SetBase = {
   isEmpty: ->
     @toList().length == 0
@@ -8,22 +10,19 @@
     @
 }

-
-
 exports.createSet = ->

-  set = Object.create(SetBase)
   data = {}

-  set.add = (name) ->
-    data[name] = true
-    @
+  inherit(SetBase, {
+    add: (name) ->
+      data[name] = true
+      @

-  set.remove = (name) ->
-    delete data[name]
-    @
+    remove: (name) ->
+      delete data[name]
+      @

-  set.toList = ->
-    Object.keys(data)
-
-  set
+    toList: ->
+      Object.keys(data)
+  })


Set path as tab title attribute

Set path as tab title attribute

--- src/tab.coffee
+++ src/tab.coffee
@@ -38,3 +38,4 @@
       fileNameText = 'untitled'

     @fileName.text(fileNameText)
+    @fileName.attr('title', @editSession.getPath())


array buffer clean

array buffer clean

--- src/wss.coffee
+++ src/wss.coffee
@@ -10,7 +10,6 @@
     onReceiveCallback = (socketInfo) ->
       console.log 'onReceive callback called'
       return if socketInfo.socketId != self.id
-      console.log(String.fromCharCode.apply(null, new Uint16Array(socketInfo.data)))

     onAcceptCallback = (socketInfo) ->
       console.log 'onAccept callback called'


Ignore callbacks if path has changed

Ignore callbacks if path has changed

--- src/packages/archive-view/lib/archive-view.coffee
+++ src/packages/archive-view/lib/archive-view.coffee
@@ -24,6 +24,8 @@
     @tree.hide()
     @loadingMessage.show()
     archive.list @path, tree: true, (error, entries) =>
+      return unless path is @path
+
       if error?
         console.error("Error listing archive file: #{@path}", error.stack ? error)
       else


only upgrade DB/cache on major versions

only upgrade DB/cache on major versions

--- chrome/content/zotero-better-bibtex/zotero-better-bibtex.coffee
+++ chrome/content/zotero-better-bibtex/zotero-better-bibtex.coffee
@@ -143,7 +143,7 @@
     observerService = Components.classes['@mozilla.org/observer-service;1'].getService(Components.interfaces.nsIObserverService)
     observerService.removeObserver(@, 'quit-application-requested')

-Zotero.BetterBibTeX.version = (version) -> ("00000#{ver}".slice(-5) for ver in version.split('.')).join('.')
+Zotero.BetterBibTeX.version = (version) -> version.split('.')[0]

 Zotero.BetterBibTeX.foreign_keys = (enabled) ->
   statement = Zotero.DB.getStatement("PRAGMA foreign_keys = #{if enabled then 'ON' else 'OFF'}", null, true)
@@ -531,9 +531,9 @@
     Zotero.BetterBibTeX.log('failed to load serialization cache:', e)
     Zotero.Translate.ItemGetter::serialized = {}

-  if Zotero.Translate.ItemGetter::serialized.Zotero != ZOTERO_CONFIG.VERSION || Zotero.Translate.ItemGetter::serialized.BetterBibTeX != @release
+  if Zotero.Translate.ItemGetter::serialized.Zotero != ZOTERO_CONFIG.VERSION || Zotero.Translate.ItemGetter::serialized.BetterBibTeX != @version(@release)
     Zotero.BetterBibTeX.log("resetting serialization cache after upgrade from Zotero #{Zotero.Translate.ItemGetter::serialized.Zotero || 'initial install'} to #{ZOTERO_CONFIG.VERSION}, BBT {Zotero.Translate.ItemGetter::serialized.BetterBibTeX || 'initial install'} to #{@release}")
-    Zotero.Translate.ItemGetter::serialized = {Zotero: ZOTERO_CONFIG.VERSION, BetterBibTeX: @release}
+    Zotero.Translate.ItemGetter::serialized = {Zotero: ZOTERO_CONFIG.VERSION, BetterBibTeX: @version(@release)}

   Zotero.Translate.ItemGetter::_serialize = (item, isAttachmentID) ->
     if isAttachmentID


rich-text aux scanner, because why not

rich-text aux scanner, because why not

--- chrome/content/zotero-better-bibtex/zotero-better-bibtex.coffee
+++ chrome/content/zotero-better-bibtex/zotero-better-bibtex.coffee
@@ -3,7 +3,10 @@

 require('Formatter.js')

-Zotero.BetterBibTeX = {}
+Zotero.BetterBibTeX = {
+  serializer: Components.classes['@mozilla.org/xmlextras/xmlserializer;1'].createInstance(Components.interfaces.nsIDOMSerializer)
+  document: Components.classes["@mozilla.org/xul/xul-document;1"].getService(Components.interfaces.nsIDOMDocument)
+}

 Zotero.BetterBibTeX.inspect = (o) ->
   clone = Object.create(null)
@@ -131,6 +134,23 @@
     observerService.removeObserver(@, 'quit-application-requested')
     return

+Zotero.BetterBibTeX.SQLColumns = (table) ->
+  statement = Zotero.DB.getStatement("pragma betterbibtex.table_info(#{table})", null, true)
+
+  # Get name column
+  for i in [0...statement.columnCount]
+    name = i if statement.getColumnName(i).toLowerCase() == 'name'
+
+  columns = null
+  while statement.executeStep()
+    columns ?= {}
+    columns[Zotero.DB._getTypedValue(statement, name)] = true
+  statement.finalize()
+
+  @log('schema', table, columns)
+
+  return columns
+
 Zotero.BetterBibTeX.init = ->
   return if @initialized
   @initialized = true
@@ -162,58 +182,43 @@
                   where f.fieldName = 'extra' and not i.itemID in (select itemID from deletedItems)"

   @pref.prefs.clearUserPref('brace-all')
-  Zotero.DB.query('create table if not exists betterbibtex._version_ (tablename primary key, version not null, unique (tablename, version))')
-  Zotero.DB.query("insert or ignore into betterbibtex._version_ (tablename, version) values ('keys', 0)")
-
-  version = Zotero.DB.valueQuery("select version from betterbibtex._version_ where tablename = 'keys'")
+
+  Zotero.DB.query("create table if not exists betterbibtex.schema (lock primary key default 'schema' check (lock='schema'), version not null)")
+
+  ### migrate '_version_' to 'schema' ###
+  if @SQLColumns('_version_')
+    Zotero.DB.query("insert or replace into betterbibtex.schema (lock, version) select 'schema', version from betterbibtex._version_ order by version desc limit 1")
+    Zotero.DB.query('drop table betterbibtex._version_')
+
+  ### initialize 'schema' ###
+  Zotero.DB.query("insert or ignore into betterbibtex.schema (lock, version) values ('schema', 0)")
+  version = Zotero.DB.valueQuery("select version from betterbibtex.schema")
   @log("Booting BBT, schema: #{version}")
-  if version < 1
-    Zotero.DB.query('create table betterbibtex.keys (itemID primary key, libraryID not null, citekey not null, pinned)')
-
-  if version < 3
-    @pref.set('scan-citekeys', true)
-
-  if version < 4
-    Zotero.DB.query('alter table betterbibtex.keys rename to keys2')
-    Zotero.DB.query('create table betterbibtex.keys (itemID primary key, libraryID not null, citekey not null, citeKeyFormat)')
-    Zotero.DB.query('insert into betterbibtex.keys (itemID, libraryID, citekey, citeKeyFormat)
-               select itemID, libraryID, citekey, case when pinned = 1 then null else ? end from betterbibtex.keys2', [@pref.get('citeKeyFormat')])
-
-  if version < 5
-    Zotero.DB.query('drop table betterbibtex.keys2')
-
-  if version < 6
-    Zotero.DB.query("
-      create table betterbibtex.cache (
-        itemID not null,
-        context not null,
-        citekey not null,
-        entry not null,
-        primary key (itemid, context))
-      ")
-
-  if version < 7
+
+  ### upgrade 'keys' table ###
+  columns = @SQLColumns('keys')
+  if columns?.pinned || columns?.libraryID
     Zotero.DB.query('alter table betterbibtex.keys rename to _keys_')
+  if !columns || columns?.pinned || columns?.libraryID
     Zotero.DB.query('create table betterbibtex.keys (itemID primary key, citekey not null, citeKeyFormat)')
-    Zotero.DB.query('insert into betterbibtex.keys (itemID, citekey, citeKeyFormat) select itemID, citekey, citeKeyFormat from betterbibtex._keys_')
+  switch
+    when columns?.pinned
+      Zotero.DB.query('insert into betterbibtex.keys (itemID, citekey, citeKeyFormat)
+                       select itemID, citekey, case when pinned = 1 then null else ? end from betterbibtex._keys_', [@pref.get('citeKeyFormat')])
+    when columns?.libraryID
+      Zotero.DB.query('insert into betterbibtex.keys (itemID, citekey, citeKeyFormat)
+                       select itemID, citekey, citeKeyFormat from betterbibtex._keys_')
+  if columns?.pinned || columns?.libraryID
     Zotero.DB.query('drop table betterbibtex._keys_')
-    @pref.set('scan-citekeys', true)
-
-  if version < 8
-    Zotero.DB.query("
-      create table if not exists betterbibtex.autoexport (
-        id integer primary key not null,
-        collection_id not null,
-        collection_name not null,
-        path not null,
-        context not null,
-        recursive not null,
-        status not null,
-        unique (collection_id, path, context))
-      ")
-
-  if version < 10
-    Zotero.DB.query('alter table betterbibtex.cache rename to _cache_')
+
+  ### drop 'keys2' table, upgrade leftovers ###
+  Zotero.DB.query('drop table betterbibtex.keys2') if @SQLColumns('keys2')
+
+  ### upgrade 'cache' table ###
+  columns = @SQLColumns('cache')
+  unless columns?.lastaccess
+    if columns
+      Zotero.DB.query('alter table betterbibtex.cache rename to _cache_')
     Zotero.DB.query("
       create table betterbibtex.cache (
         itemID not null,
@@ -223,10 +228,29 @@
         lastaccess not null default CURRENT_TIMESTAMP,
         primary key (itemid, context))
       ")
-    Zotero.DB.query('insert into betterbibtex.cache (itemID, context, citekey, entry) select itemID, context, citekey, entry from betterbibtex._cache_')
-    Zotero.DB.query('drop table betterbibtex._cache_')
-
-  Zotero.DB.query("insert or replace into betterbibtex._version_ (tablename, version) values ('keys', 10)")
+    unless columns.lastaccess
+      Zotero.DB.query('insert into betterbibtex.cache (itemID, context, citekey, entry) select itemID, context, citekey, entry from betterbibtex._cache_')
+      Zotero.DB.query('drop table betterbibtex._cache_')
+
+  ### upgrade 'autoexport' table ###
+  Zotero.DB.query("
+    create table if not exists betterbibtex.autoexport (
+      id integer primary key not null,
+      collection_id not null,
+      collection_name not null,
+      path not null,
+      context not null,
+      recursive not null,
+      status not null,
+      unique (collection_id, path, context))
+    ")
+
+  ### schema 3 & 7 had a scanning flaw, force rescan ###
+  if version < 7
+    @pref.set('scan-citekeys', true)
+
+  ### mark current version ###
+  Zotero.DB.query("insert or replace into betterbibtex.schema (lock, version) values ('schema', 11)")

   Zotero.Translate.Export::Sandbox.BetterBibTeX = {
     __exposedProps__: {keymanager: 'r', cache: 'r'}
@@ -408,6 +432,7 @@

       continue if extra.translator != 'BibTeX AUX Scanner'
       Zotero.BetterBibTeX.log('::: AUX', collection.id, extra.citations)
+      Zotero.Items.trash([itemID])

       missing = []
       for citekey in extra.citations
@@ -418,16 +443,27 @@
           collection.addItem(id)
         else
           Zotero.BetterBibTeX.log("::: citekey #{citekey} missing")
-          missing.push("* #{citekey}")
+          missing.push(citekey)

       if missing.length == 0
         Zotero.BetterBibTeX.log("::: all citekeys found")
-        Zotero.Items.trash([itemID])
       else
-        Zotero.BetterBibTeX.log("::: #{missing.length} citekeys missing")
-        item = Zotero.Items.get(itemID)
-        item.setField('extra', "Missing references:\n#{missing.join('\n')}")
+        report = new Zotero.BetterBibTeX.HTMLNode('http://www.w3.org/1999/xhtml', 'html')
+        report.div(->
+          @p(-> @b('BibTeX AUX scan'))
+          @p('Missing references:')
+          @ul(->
+            for citekey in missing
+              @li(citekey)
+            return
+          )
+          return
+        )
+        item = new Zotero.Item('note')
+        item.libraryID = collection.libraryID
+        item.setNote(report.serialize())
         item.save()
+        collection.addItem(item.id)

     unless collections.length == 0 || Zotero.BetterBibTeX.pref.get('auto-export') == 'disabled'
       Zotero.DB.query("update betterbibtex.autoexport set status = 'pending' where collection_id in #{Zotero.BetterBibTeX.SQLSet(collections)}")
@@ -574,6 +610,69 @@
   throw 'format: no item\n' + (new Error('dummy')).stack if not item.itemType
   return item

+class Zotero.BetterBibTeX.XmlNode
+  constructor: (@namespace, @root, @doc) ->
+    if !@doc
+      @doc = Zotero.BetterBibTeX.document.implementation.createDocument(@namespace, @root, null)
+      @root = @doc.documentElement
+
+  serialize: -> Zotero.BetterBibTeX.serializer.serializeToString(@doc)
+
+  alias: (names) ->
+    for name in names
+      @Node::[name] = do (name) -> (v...) -> XmlNode::add.apply(@, [{"#{name}": v[0]}].concat(v.slice(1)))
+    return
+
+  set: (node, attrs...) ->
+    for attr in attrs
+      for own name, value of attr
+        switch
+          when typeof value == 'function'
+            value.call(new @Node(@namespace, node, @doc))
+
+          when name == ''
+            node.appendChild(@doc.createTextNode('' + value))
+
+          else
+            node.setAttribute(name, '' + value)
+    return
+
+  add: (content...) ->
+    if typeof content[0] == 'object'
+      for own name, attrs of content[0]
+        continue if name == ''
+        node = @doc.createElementNS(@namespace, name)
+        @root.appendChild(node)
+        content = [attrs].concat(content.slice(1))
+        break # there really should only be one pair here!
+    node ?= @root
+
+    content = (c for c in content when typeof c == 'number' || c)
+
+    for attrs in content
+      switch
+        when typeof attrs == 'string'
+          node.appendChild(@doc.createTextNode(attrs))
+
+        when typeof attrs == 'function'
+          attrs.call(new @Node(@namespace, node, @doc))
+
+        when attrs.appendChild
+          node.appendChild(attrs)
+
+        else
+          @set(node, attrs)
+
+    return
+
+class Zotero.BetterBibTeX.HTMLNode extends Zotero.BetterBibTeX.XmlNode
+  constructor: (@namespace, @root, @doc) ->
+    super(@namespace, @root, @doc)
+
+  Node: HTMLNode
+
+  HTMLNode::alias(['b', 'p', 'div', 'ul', 'li'])
+
 require('preferences.coffee')
 require('keymanager.coffee')
 require('web-endpoints.coffee')


Client: Activity: Updated dropboxMixin onEnter event handler to invite members by groupAdmin

--- client/activity/lib/components/dropbox/dropboxinputmixin.coffee
+++ client/activity/lib/components/dropbox/dropboxinputmixin.coffee
@@ -1,6 +1,9 @@
 kd            = require 'kd'
-React          = require 'kd-react'
-keyboardKeys   = require 'app/util/keyboardKeys'
+React         = require 'kd-react'
+keyboardKeys  = require 'app/util/keyboardKeys'
+showError     = require 'app/util/showError'
+validator     = require 'validator'
+ActivityFlux  = require 'activity/flux'

 module.exports = DropboxInputMixin =
@@ -33,6 +36,16 @@

       dropdown.confirmSelectedItem()

+    else if @isGroupAdmin and @isGroupAdmin()
+
+      value        = event.target.value.trim()
+      isValidEmail = validator.isEmail value
+
+      return showError 'That doesn\'t seem like a valid email address.'  unless isValidEmail
+
+      ActivityFlux.actions.channel.inviteMember [{email: value}], =>
+        @setState value: ''
+

   onEsc: (event) -> @getDropdown().close()


reactivity: fix for removing private message in realtime

--- client/activity/lib/flux/actions/realtime/actioncreators.coffee
+++ client/activity/lib/flux/actions/realtime/actioncreators.coffee
@@ -88,7 +88,11 @@
           _loadMessageFn { channel, channelMessage: _message }
           _dispatchFn { unreadCount: payload.unreadCount, channel }

-    .on 'MessageRemovedFromChannel', _dispatchFn
+    .on 'MessageRemovedFromChannel', (payload) ->
+      { channel, channelMessage, unreadCount } = payload
+      dispatch actions.REMOVE_MESSAGE_SUCCESS, { messageId: channelMessage.id }
+      _dispatchFn { unreadCount, channel }
+
     .on 'RemovedFromChannel', _dispatchFn
     .on 'ReplyAdded', _dispatchFn
     .on 'ParticipantUpdated', _dispatchFn


client/app: remove ActivityAutoCompleteUserItemView

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

--- client/app/lib/activity/activityautocompleteuseritemview.coffee
+++ client/app/lib/activity/activityautocompleteuseritemview.coffee
@@ -1,29 +0,0 @@
-kd = require 'kd'
-KDAutoCompleteListItemView = kd.AutoCompleteListItemView
-AvatarStaticView = require 'app/commonviews/avatarviews/avatarstaticview'
-AutoCompleteProfileTextView = require 'app/commonviews/linkviews/autocompleteprofiletextview'
-
-
-module.exports = class ActivityAutoCompleteUserItemView extends KDAutoCompleteListItemView
-
-  constructor: (options, data) ->
-
-    options.type = 'dropdown-member'
-
-    super options, data
-
-
-  viewAppended: ->
-
-    userInput = @getOptions().userInput or @getDelegate().userInput
-
-    @addSubView new AvatarStaticView
-      size      :
-        width   : 25
-        height  : 25
-    , @getData()
-
-    @addSubView @profileLink = new AutoCompleteProfileTextView {
-      shouldShowNick : yes
-      userInput
-    }, @getData()


SidebarMachineSharePopup: Override destroy to destroy the overlay.

--- client/app/lib/activity/sidebar/sidebarmachinesharepopup.coffee
+++ client/app/lib/activity/sidebar/sidebarmachinesharepopup.coffee
@@ -125,3 +125,9 @@
       @destroy()
       envDataProvider.fetch =>
         kd.singletons.mainView.activitySidebar.redrawMachineList()
+
+  destroy: ->
+
+    @overlay?.destroy()
+
+    super


remove some more groups stuff

remove some more groups stuff

--- client/landing/app/MainApp/maincontroller/maincontroller.coffee
+++ client/landing/app/MainApp/maincontroller/maincontroller.coffee
@@ -16,7 +16,7 @@
     KD.registerSingleton "kiteController", new KiteController
     KD.registerSingleton "contentDisplayController", new ContentDisplayController
     KD.registerSingleton "notificationController", new NotificationController
-    KD.registerSingleton "groupsController", new GroupsController this
+    # KD.registerSingleton "groupsController", new GroupsController this

     @appReady =>


minor

minor

--- client/landing/app/MainApp/maincontroller/maincontroller.coffee
+++ client/landing/app/MainApp/maincontroller/maincontroller.coffee
@@ -32,7 +32,6 @@
     KD.registerSingleton "vmController",              new VirtualizationController
     KD.registerSingleton "contentDisplayController",  new ContentDisplayController
     KD.registerSingleton "notificationController",    new NotificationController
-    KD.registerSingleton "paymentController",         new NotificationController
     KD.registerSingleton "linkController",            new LinkController
     KD.registerSingleton 'router',           router = new KodingRouter location.pathname


[Logout] logout location.reload() done after cookie is just changed

--- client/landing/app/MainApp/maincontroller/maincontroller.coffee
+++ client/landing/app/MainApp/maincontroller/maincontroller.coffee
@@ -96,9 +96,10 @@
   doLogout:->
     KD.logout()
     KD.remote.api.JUser.logout (err, account, replacementToken)=>
-      $.cookie 'clientId', replacementToken if replacementToken
       @_logoutAnimation()
-      KD.utils.wait 1100, -> location.reload()
+      KD.utils.wait 1000, ->
+        $.cookie 'clientId', replacementToken  if replacementToken
+        location.reload()

   _logoutAnimation:->
     mainView      = KD.getSingleton("mainView")


Handle grouped graphics

Handle grouped graphics

--- src/draw_grid.coffee
+++ src/draw_grid.coffee
@@ -20,8 +20,9 @@
       fill: "#ccffcc"
       stroke: "black"
     }
-    S.g {class: "xgrid-lines"},[1..grid_split-1].map (n) ->
-      draw_xgrid(n*cell_size)
-    S.g {class: "ygrid-lines"},[1..grid_split-1].map (n) ->
-      draw_ygrid(n*cell_size)
+    S.g {class: "grid-lines"},[1..grid_split-1].map (n) ->
+      S.g {class: "grid-lines n#{n}"}, [
+        draw_xgrid(n*cell_size)
+        draw_ygrid(n*cell_size)
+      ]
   ]


fix event name for resuming

fix event name for resuming

--- src/dt-stream.coffee
+++ src/dt-stream.coffee
@@ -128,7 +128,7 @@
     resume: () ->
         if @paused
             @emit 'resume'
-            @template.emit 'pause'
+            @template.emit 'resume'
             @paused = no
         @queue.shift()?() while not @paused and @queue.length
         if @closed? and not @queue.length


- Cleanup messy code, add wrapper css class to enable scrolling

--- client/app/CommonViews/LinkViews.coffee
+++ client/app/CommonViews/LinkViews.coffee
@@ -181,37 +181,36 @@
     else if participants[0] instanceof bongo.api.JTag
       @type = "tag"

-    options.title   = titleMap()[@type]
-    options.height  = "auto"
-    options.overlay = yes
+    options.title    = titleMap()[@type]
+    options.height   = "auto"
+    options.overlay  = yes
+    options.cssClass = "modal-topic-wrapper"

     super

   viewAppended:->
-    @putList()
-
-  putList:->
+    @prepareList()
+
+  putList: (participants) ->
+    controller = new KDListViewController
+      view            : new KDListView
+        subItemClass  : listItemMap()[@type]
+        cssClass      : "modal-topic-list"
+    ,
+      items           : participants
+
+    @addSubView controller.getView(), ".kdmodal-content"
+
+  prepareList:->

     {group} = @getOptions()
+
     if group
       bongo.cacheable group, (err, participants)=>
-        controller = new KDListViewController
-          view            : new KDListView
-            subItemClass  : listItemMap()[@type]
-            cssClass      : "modal-topic-list"
-        ,
-          items           : participants
-
-        @addSubView controller.getView(), ".kdmodal-content"
+        if err then warn err
+        else @putList participants
     else
-      controller = new KDListViewController
-          view            : new KDListView
-            subItemClass  : listItemMap()[@type]
-            cssClass      : "modal-topic-list"
-        ,
-          items           : @getData()
-
-        @addSubView controller.getView(), ".kdmodal-content"
+      @putList @getData()

 class AvatarView extends LinkView
   constructor:(options,data)->


newrelic: Add missing 'role' to GuvScaled event

Not so useful without this :)

--- src/newrelic.coffee
+++ src/newrelic.coffee
@@ -19,6 +19,7 @@
       app: data.app
       jobs: data.current_jobs
       workers: data.new_workers
+      role: role
     debug 'recording event', 'GuvScaled'
     nr.recordCustomEvent 'GuvScaled', event


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/expirable.coffee
+++ src/expirable.coffee
@@ -6,7 +6,7 @@

   startingAlpha = I.alpha

-  self.bind "afterUpdate", ->
+  self.bind "update", ->
     log I.age
     log I.duration
     if I.fadeOut


info context didn't mean much

info context didn't mean much

--- src/notebook.coffee
+++ src/notebook.coffee
@@ -43,7 +43,7 @@
       lead_string = lead.to_string object
       if $.type(object) == 'function'
         @cli.text "#{lead_string} is a Graphite function"
-        run_in_info_context @input_cell, "docs #{object.values[0]}"
+        run_before @input_cell, "docs #{object.values[0]}"
       else
         @cli.text "What do you want to do with #{lead_string}?"
         for f in ['data', 'graph', 'img', 'url']
@@ -86,6 +86,7 @@
     for cell in notebook.cells
       cell.active = false
     notebook.cells.length = 0
+

   cell_index = (cell) ->
     cell.notebook.cells.indexOf cell
@@ -148,8 +149,8 @@
     set_cell_value cell, opts.code if opts.code?
     cell

-  # Add an input cell above the last input cell
-  run_in_info_context = (current_cell, code) ->
+  # run an input cell above the current cell
+  run_before = (current_cell, code) ->
     cell = add_input_cell current_cell.notebook, code: code, before: current_cell
     cell.run()

@@ -493,13 +494,13 @@
     handle_file: handle_file

     save: (cell) ->
-      run_in_info_context cell, 'save'
+      run_before cell, 'save'

     context_help: (cell, token) ->
       if graphite.has_docs token
-        run_in_info_context cell, "docs '#{token}'"
+        run_before cell, "docs '#{token}'"
       else if ops[token]?
-        run_in_info_context cell, "help #{token}"
+        run_before cell, "help #{token}"

     move_focus: (cell, offset) ->
       new_cell = input_cell_at_offset cell, offset


testrunner: restructure test runner

testrunner: restructure test runner

--- client/testrunner/lib/routehandler.coffee
+++ client/testrunner/lib/routehandler.coffee
@@ -2,12 +2,35 @@
 kd                   = require 'kd'
 KodingAppsController = require 'app/kodingappscontroller'

+RunnerSocketConnector = require './runnersocketconnector'
+
 addToHead = KodingAppsController.appendHeadElement.bind KodingAppsController
+
+SOCKET_PORT = 1777

 module.exports = -> lazyrouter.bind 'testrunner', (type, info, state, path, ctx) ->

   switch type
     when 10 then runTests()
+
+
+appendScripts = (callback) ->
+
+  mochaOptions =
+    identifier : 'mocha'
+    url        : 'https://cdnjs.cloudflare.com/ajax/libs/mocha/2.2.4/mocha.js'
+
+  cssOptions =
+    identifier : 'mocha-css'
+    url        : 'https://cdnjs.cloudflare.com/ajax/libs/mocha/2.2.4/mocha.css'
+
+  socketIoOptions =
+    identifier : 'socket-io'
+    url        : 'https://cdn.socket.io/socket.io-1.2.0.js'
+
+  addToHead 'style', cssOptions, ->
+    addToHead 'script', mochaOptions, ->
+      addToHead 'script', socketIoOptions, callback

 runTests = ->
@@ -17,23 +40,40 @@
   mochaContainer.id = 'mocha'
   document.body.appendChild mochaContainer

-  jsOptions =
-    identifier : 'mocha'
-    url        : 'https://cdnjs.cloudflare.com/ajax/libs/mocha/2.2.4/mocha.js'
+  appendScripts =>

-  cssOptions =
-    identifier : 'mocha-css'
-    url        : 'https://cdnjs.cloudflare.com/ajax/libs/mocha/2.2.4/mocha.css'
+    window.socket = io "http://localhost:#{SOCKET_PORT}"
+    runMocha mochaContainer, socket

-  addToHead 'style', cssOptions, ->
-    addToHead 'script', jsOptions, ->
-      mocha.ui('bdd')
-      mochaTests = require './require-tests'
-      runner = mocha.run()

-      runner.on 'end', ->
-        el           = document.createElement 'div'
-        el.id        = 'tests-completed'
-        el.innerHTML = 'All tests finished'
+runMocha = (mochaContainer, socket) ->

-        mochaContainer.appendChild el
+  mocha.ui('bdd')
+
+  mochaTests = require './require-tests'
+
+  runner = mocha.run()
+
+  connector = new RunnerSocketConnector runner, socket
+
+  # the reporters relying on the 'start' event of mocha runner. but runner
+  # instance is being created before the connector itself. we are simulating
+  # the start event so that the reporters and other stuff can work as expected.
+  connector.simulateRunnerStartEvent()
+
+  runner.on 'end', -> handleRunnerEnd mochaContainer, runner
+
+  # server sends reload requests to re-run tests. simply refresh.
+  socket.on 'reload', -> wait 50, -> window.location.reload()
+
+
+handleRunnerEnd = (mochaContainer) ->
+
+  el           = document.createElement 'div'
+  el.id        = 'tests-completed'
+  el.innerHTML = 'All tests finished'
+
+  mochaContainer.appendChild el
+
+
+wait = (delay, fn) -> setTimeout fn, delay


default 404 handling

default 404 handling

--- src/core/voicious.coffee
+++ src/core/voicious.coffee
@@ -44,6 +44,8 @@
                     if @app[method]?
                         for route of service.Routes[method]
                             @app[method] route, service.Routes[method][route]
+        @app.all /^(?!\/public)\/*/, (req, res) =>
+            throw new Errors.NotFound

     configure       : () =>
         if not @connectedToDb
@@ -63,8 +65,10 @@
         do @setAllRoutes
         @app.use (err, req, res, next) =>
             if err instanceof Errors.NotFound
+                res.status 404
                 res.render 'error/404.jade'
             else
+                res.status 500
                 res.render 'error/500.jade'
         @configured = yes


mouseTrackingView: rename vars {dx,dy} -> {x,y}

an offset, unlike a displacement, is not a delta

--- coffee/src/views/mouseTrackingView.coffee
+++ coffee/src/views/mouseTrackingView.coffee
@@ -259,9 +259,9 @@

   _mouseOffsetFromElement: ($el) =>
     $el = $ $el
-    dx = util.mouseLocationTracker.x - $el.offset().left
-    dy = util.mouseLocationTracker.y - $el.offset().top
-    x: dx, y: dy
+    x = util.mouseLocationTracker.x - $el.offset().left
+    y = util.mouseLocationTracker.y - $el.offset().top
+    x: x, y: y

   # current mouse position x and y displacement from coordinates; uses mousedown


Update ToDo list with information about lobcoveragejs

--- src/d3.timeslider.coffee
+++ src/d3.timeslider.coffee
@@ -2,6 +2,7 @@

     # TODO
     #  * Allow for the registration of datasets to be shown beneath the brush
+    #    * WCS  DescribeEOCoverageSet (using libcoveragejs (schindlerf))
     #  * Rename pixelPerDay to something more generic (could also be hours, ... depending on the time frame)
     #  * Cleanup the mess that is the axis labels right now
     #  * Compute the padding at the left & right of the timeslider


Properly export symbols

Properly export symbols

--- src/reactive.coffee
+++ src/reactive.coffee
@@ -1,8 +1,11 @@
+if typeof exports is 'undefined'
+  @rx = rx = {}
+else
+  rx = exports
+
 #
 # Reactivity
 #
-
-rx = {}

 class Recorder
   constructor: ->
@@ -257,7 +260,10 @@
 # reactive template DSL
 #

-rxt = {}
+if typeof exports is 'undefined'
+  @rxt = rxt = {}
+else
+  rxt = exports

 rxt.mktag = mktag = (tag) ->
   (attrs, contents) ->


Stringify JSON and parse to copy

Stringify JSON and parse to copy

This is a dirty hack to clone an object without adding dependencies to hubot.

--- src/response.coffee
+++ src/response.coffee
@@ -7,6 +7,11 @@
   # message - A Message instance.
   # match   - A Match object from the successful Regex match.
   constructor: (@robot, @message, @match) ->
+    # Dirty hack to deep clone without a dependency...
+    json = JSON.stringify @message
+    message = JSON.parse json
+    @message = message
+

   # Public: Posts a message back to the chat source
   #