Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/function_extensions.coffee
+++ test/function_extensions.coffee
@@ -22,7 +22,7 @@
   ok calledAfter, "Called after"

 test "#once", ->
-  onc
+  onceScore = ->

 test "#returning", ->
   x = 0


use browser.reload() rather than browser.visit()

--- test/integration/dataset.coffee
+++ test/integration/dataset.coffee
@@ -97,7 +97,7 @@

           context 'when I revisit the homepage', ->
             before (done) ->
-              browser.visit "#{url}/", done
+              browser.reload done

             it 'the dataset stays hidden', ->
               browser.text().should.not.include randomname


coordinator: Better error on not finding named port

--- src/coordinator.coffee
+++ src/coordinator.coffee
@@ -153,6 +153,8 @@
       return if not @started
       handler msg
       @broker.ackMessage msg
+
+    return callback new Error "Could not find outport #{outport} for role #{participantId}" if not port

     # Cannot subscribe directly to an outqueue, must create and bind an inqueue
     readQueue = 'msgflo-export-' + Math.floor(Math.random()*999999)


Add safety

Add safety

--- src/coordinator.coffee
+++ src/coordinator.coffee
@@ -212,7 +212,7 @@
     return @library.getSource component, callback

   startParticipant: (node, component, callback) ->
-    if node in @options.ignore
+    if @options.ignore?.length and node in @options.ignore
       console.log "WARNING: Not restarting ignored participant #{node}"
       return callback null
     if not @library.components[component]?.command


修正EntityList.last()等方法不可用的BUG，同时添加getLast()等

--- src/core/entity.coffee
+++ src/core/entity.coffee
@@ -1403,7 +1403,7 @@
 		return

 	_findPrevious: (entity)->
-		if not (entity?.parent isnt @)
+		if not entity or entity.parent is @
 			if entity
 				page = entity._page
 				if page[page.hotIndex] is entity
@@ -1425,7 +1425,7 @@
 		return []

 	_findNext: (entity)->
-		if not (entity?.parent isnt @)
+		if not entity or entity.parent is @
 			if entity
 				page = entity._page
 				if page[page.hotIndex] is entity
@@ -1624,8 +1624,8 @@

 		if not @_dontChangeCurrent and not @current
 			@setCurrent(entity)
-			if 0 <= index < entity.page.length
-				entity.page.hotIndex = index
+			if 0 <= index < entity._page.length
+				entity._page.hotIndex = index
 		return entity

 	remove: (entity, detach)->
@@ -1729,7 +1729,7 @@
 		[entity, index] = @_findNext()
 		if entity
 			@setCurrent(entity)
-			entity.page.hotIndex = index
+			entity._page.hotIndex = index
 			return entity
 		else
 			return @current
@@ -1738,7 +1738,7 @@
 		[entity, index] = @_findPrevious(@current)
 		if entity
 			@setCurrent(entity)
-			entity.page.hotIndex = index
+			entity._page.hotIndex = index
 			return entity
 		else
 			return @current
@@ -1747,7 +1747,7 @@
 		[entity, index] = @_findNext(@current)
 		if entity
 			@setCurrent(entity)
-			entity.page.hotIndex = index
+			entity._page.hotIndex = index
 			return entity
 		else
 			return @current
@@ -1756,10 +1756,26 @@
 		[entity, index] = @_findPrevious()
 		if entity
 			@setCurrent(entity)
-			entity.page.hotIndex = index
+			entity._page.hotIndex = index
 			return entity
 		else
 			return @current
+
+	getFirst: ()->
+		[entity, index] = @_findNext()
+		return entity
+
+	getPrevious: ()->
+		[entity, index] = @_findPrevious(@current)
+		return entity
+
+	getNext: ()->
+		[entity, index] = @_findNext(@current)
+		return entity
+
+	getLast: ()->
+		[entity, index] = @_findPrevious()
+		return entity

 	hasPrevious: ()->
 		return @_findPrevious(@current).length > 0


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/core_object.coffee
+++ src/core_object.coffee
@@ -40,6 +40,29 @@

     ###*
     Generates a public jQuery style getter method for each String argument.
+
+

+
+    myObject = Core
+      r: 255
+      g: 0
+      b: 100
+
+    myObject.attrReader "r", "g", "b"
+
+    myObject.r()
+
+    => 255
+
+    myObject.g()
+
+    => 0
+
+    myObject.b()
+
+    => 100
+
+

     @name attrReader
     @methodOf Core#


adding findUser

adding findUser

--- src/scripts/working-on.coffee
+++ src/scripts/working-on.coffee
@@ -15,6 +15,15 @@
 #   beezee

 module.exports = (robot) ->
+
+  findUser = (name) ->
+    users = robot.usersForFuzzyName name
+    if users.length is 1
+      users[0]
+    else if users.length > 1
+      users
+    else
+      false

   robot.respond /(what\'s|what is) (everyone|everybody) working on(\?)?/i, (msg) ->


allowed negative fractional axis for catenate, fixed #84

--- src/vocabulary/comma.coffee
+++ src/vocabulary/comma.coffee
@@ -25,7 +25,7 @@
       if axis
         axis = axis.unwrap()
         if typeof axis isnt 'number' then domainError()
-        if nAxes and !(0 <= axis < nAxes) then rankError()
+        if nAxes and !(-1 < axis < nAxes) then rankError()
       else
         axis = nAxes - 1


Disallow selecting dots on infinite pagination carousel

--- src/desktop/apps/partner2/components/hero_shows_carousel/view.coffee
+++ src/desktop/apps/partner2/components/hero_shows_carousel/view.coffee
@@ -81,7 +81,6 @@

           $selectedDot.removeClass('is-deemphasized')

-      ($dots = @$('.mgr-dot')).on 'click', -> flickity.select $dots.index $(this)
       @$('.js-mgr-prev').on 'click', -> flickity.previous()
       @$('.js-mgr-next').on 'click', -> flickity.next()
     ) if partnerShows.length > 1


Fix listener for issues on sidebar

Fix listener for issues on sidebar

--- admin/static/coffee/sidebar.coffee
+++ admin/static/coffee/sidebar.coffee
@@ -97,6 +97,9 @@
                 @.$('.issues').html @issues.render().el

                 return @
+
+        destroy: =>
+            window.app.off 'all', @render

     class @Logs extends Backbone.View
         className: 'recent-log-entries'
@@ -156,13 +159,12 @@
         tagName: 'div'
         template: Handlebars.compile $('#sidebar-client_connection_status-template').html()

-        initialize: ->
-            log_initial '(initializing) client connection status view'
-            connection_status.on 'all', => @render()
-            datacenters.on 'all', => @render()
-            machines.on 'all', => @render()
-
-        render: ->
+        initialize: =>
+            connection_status.on 'all', @render
+            datacenters.on 'all', @render
+            machines.on 'all', @render
+
+        render: =>
             log_render '(rendering) status panel view'
             connected_machine = machines.get(connection_status.get('contact_machine_id'))
             json =
@@ -179,6 +181,11 @@

             return @

+        destroy: =>
+            connection_status.off 'all', @render
+            datacenters.off 'all', @render
+            machines.off 'all', @render
+
     # Sidebar.ConnectivityStatus
     class @ConnectivityStatus extends Backbone.View
         className: 'connectivity-status'
@@ -186,9 +193,9 @@

         initialize: =>
             # Rerender every time some relevant info changes
-            directory.on 'all', (model, collection) => @render()
-            machines.on 'all', (model, collection) => @render()
-            datacenters.on 'all', (model, collection) => @render()
+            directory.on 'all', @render
+            machines.on 'all', @render
+            datacenters.on 'all', @render

         compute_connectivity: =>
             # data centers visible
@@ -209,6 +216,12 @@
             @.$el.html @template @compute_connectivity()
             return @

+        destroy: =>
+            # Rerender every time some relevant info changes
+            directory.off 'all', @render
+            machines.off 'all', @render
+            datacenters.off 'all', @render
+
     # Sidebar.Issues
     class @Issues extends Backbone.View
         className: 'issues'
@@ -217,7 +230,7 @@

         initialize: =>
             log_initial '(initializing) sidebar view: issues'
-            issues.on 'all', => @render()
+            issues.on 'all', @render

         render: =>
             # Group critical issues by type
@@ -247,3 +260,6 @@
                     )
                 no_issues: _.keys(critical_issues).length is 0 and _.keys(other_issues).length is 0
             return @
+
+        destroy: =>
+            issues.off 'all', @render


Fix indentation in static/js/party-list.coffee

--- static/js/party-list.coffee
+++ static/js/party-list.coffee
@@ -1,46 +1,46 @@
 class PartyListView extends Backbone.View
-	el: $('div.party-list')
-	el_gov: $('ul.party-list-government')
-	el_oppo: $('ul.party-list-opposition')
+    el: $('div.party-list')
+    el_gov: $('ul.party-list-government')
+    el_oppo: $('ul.party-list-opposition')

-	render: ->
-		@collection.forEach @renderOne, @
-		return @
+    render: ->
+        @collection.forEach @render_one, @
+        return @

-	renderOne: (party) ->
-		if party.get "governing_party"
-			el = @el_gov
-		else
-			el = @el_oppo
-		partyView = new PartyView
-			model: party
-			# Directly render into the container ul on the Django template
-			# Will this break any event listeners?
-			el: el
-		partyView.render().el
+    render_one: (party) ->
+        if party.get "governing_party"
+            el = @el_gov
+        else
+            el = @el_oppo
+        party_view = new PartyListItemView
+            model: party
+            # Directly render into the container ul on the Django template
+            # Will this break any event listeners?
+            el: el
+        party_view.render().el

-	initialize: ->
-		@collection = new PartyList
-		@collection.comparator = 'member_count'
-		# This gets triggered when the content has fetch'ed from JSON
-		@listenTo @collection, "reset", @render
-		@collection.fetch
-			reset: true
+    initialize: ->
+        @collection = new PartyList
+        @collection.comparator = 'member_count'
+        # This gets triggered when the content has fetch'ed from JSON
+        @listenTo @collection, "reset", @render
+        @collection.fetch
+            reset: true

-class @PartyView extends Backbone.View
-	template: _.template $('#party-list-item-template').html()
+class @PartyListItemView extends Backbone.View
+    template: _.template $('#party-list-item-template').html()

-	render: ->
-		template_variables = @model.toJSON()
-		# This is a view-specific transformation, would seem out of place in model
-		if template_variables.governing_party
-			template_variables.party_governing_status = "party-government"
-		else
-			template_variables.party_governing_status = "party-opposition"
-		template_variables.view_url = @model.get_view_url()
-		# Appending not a good idea?
-		@$el.append @template template_variables
-		return @
+    render: ->
+        template_variables = @model.toJSON()
+        # This is a view-specific transformation, would seem out of place in model
+        if template_variables.governing_party
+            template_variables.party_governing_status = "party-government"
+        else
+            template_variables.party_governing_status = "party-opposition"
+        template_variables.view_url = @model.get_view_url()
+        # Appending not a good idea?
+        @$el.append @template template_variables
+        return @

 # Initialize the page contents
-@partyListView = new PartyListView+@party_list_view = new PartyListView


ConfigParser throw error when file not found

--- src/configParser.coffee
+++ src/configParser.coffee
@@ -17,8 +17,8 @@
     return false

   _loadYamlFile: (path) ->
-    return yml.safeLoad fs.readFileSync path, 'utf8' if fs.existsSync path
-    return {}
+    throw new Error "Config not found: #{path}" if not fs.existsSync path
+    return yml.safeLoad fs.readFileSync path, 'utf8'

   _processIncludeDirective: (path, config, included) ->
     return config if not config.include


User configfilter to expand module names. Needed for AtomLinter/linter-coffeelint#25

--- src/configfinder.coffee
+++ src/configfinder.coffee
@@ -6,6 +6,7 @@
 fs = require 'fs'
 path = require 'path'
 stripComments = require 'strip-json-comments'
+resolve = require('resolve').sync

 # Cache for findFile
 findFileResults = {}
@@ -41,11 +42,7 @@
 # given), as either the package.json's 'coffeelintConfig' property, or a project
 # specific 'coffeelint.json' or a global 'coffeelint.json' in the home
 # directory.
-exports.getConfig = (filename = null) ->
-    if filename
-        dir = path.dirname(path.resolve(filename))
-    else
-        dir = process.cwd()
+getConfig = (dir) ->

     npmConfig = loadNpmConfig(dir)
     return npmConfig  if npmConfig
@@ -56,3 +53,40 @@
     home = path.normalize(path.join(envs, "coffeelint.json"))
     if fs.existsSync(home)
         return loadJSON(home)
+
+# configfinder is the only part of coffeelint that actually has the full
+# filename and can accurately resolve module names. This will find all of the
+# modules and expand them into full paths so that they can be found when the
+# source and config are passed to `coffeelint.lint`
+expandModuleNames = (dir, config) ->
+    for ruleName, data of config when data?.module?
+        config[ruleName].module = resolve data.module, {
+            basedir: dir
+        }
+
+    coffeelint = config.coffeelint
+    if coffeelint?.transforms?
+        coffeelint.transforms = coffeelint.transforms.map (moduleName) ->
+            return resolve moduleName, {
+                basedir: dir
+            }
+    if coffeelint?.coffeescript?
+        coffeelint.coffeescript = resolve coffeelint.coffeescript, {
+            basedir: dir
+        }
+
+    config
+
+exports.getConfig = (filename = null) ->
+    if filename
+        dir = path.dirname(path.resolve(filename))
+    else
+        dir = process.cwd()
+
+    config = getConfig(dir)
+
+    console.log('config', config)
+    if config
+        config = expandModuleNames(dir, config)
+
+    config


synctable DEBUG option

synctable DEBUG option

--- src/smc-util/synctable.coffee
+++ src/smc-util/synctable.coffee
@@ -81,6 +81,9 @@
 table is set on server version of table, so reconnecting user only sends its changes if upstream
 hasn't changed anything in that same record.
 ###
+
+# if true, will log to the console a huge amount of info about every get/set
+DEBUG = true

 {EventEmitter} = require('events')

@@ -574,6 +577,8 @@
         if not @_value_server?
             console.warn("_update_change(#{@_table}): tried to call _update_change even though set not yet defined (ignoring)")
             return
+        if DEBUG
+            console.log("_update_change('#{@_table}'): #{misc.to_json(change)}")
         @emit('before-change')
         changed_keys = []
         conflict = false
@@ -667,7 +672,12 @@
             merge = 'deep'

         if not immutable.Map.isMap(changes)
-            cb?("type error -- changes must be an immutable.js Map or JS map"); return
+            cb?("type error -- changes must be an immutable.js Map or JS map")
+            return
+
+        if DEBUG
+            console.log("set('#{@_table}'): #{misc.to_json(changes.toJS())}")
+
         # Ensure that each key is allowed to be set.
         if not @_client_query.set?
             cb?("users may not set #{@_table}")


KodingProvider:create: much better flow since guessNextLabel can take time let's do it first

--- workers/social/lib/social/models/computeproviders/koding.coffee
+++ workers/social/lib/social/models/computeproviders/koding.coffee
@@ -103,20 +103,20 @@

     storage ?= 3

-    @fetchUserPlan client, (err, userPlan)=>
-
-      # Commented-out this since if it fails to fetch
-      # plan it uses 'free' as default. ~ GG
-      # return callback err  if err?
-
-      @fetchUsage client, options, (err, usage)->
-
-        return callback err  if err?
-
-        if err = checkUsage usage, userPlan, storage
-          return callback err
-
-        guessNextLabel user, group, label, (err, label)->
+    guessNextLabel user, group, label, (err, label)=>
+
+      @fetchUserPlan client, (err, userPlan)=>
+
+        # Commented-out this since if it fails to fetch
+        # plan it uses 'free' as default. ~ GG
+        # return callback err  if err?
+
+        @fetchUsage client, options, (err, usage)->
+
+          return callback err  if err?
+
+          if err = checkUsage usage, userPlan, storage
+            return callback err

           meta =
             type          : "amazon"


Fix security issue with JPrivateMessage.create

--- workers/social/lib/social/models/messages/privatemessage.coffee
+++ workers/social/lib/social/models/messages/privatemessage.coffee
@@ -1,5 +1,7 @@
-JPost = require './post'
-JComment = require './comment'
+
+JPost       = require './post'
+JComment    = require './comment'
+KodingError = require '../../error'

 module.exports = class JPrivateMessage extends JPost

@@ -60,8 +62,14 @@

     secure (client, data, callback)->
       {delegate} = client.connection
+
+      JAccount = require '../account'
+
+      unless delegate instanceof JAccount
+        callback new KodingError 'Access denied.'
+        return no
+
       {to, subject, body} = data
-      JAccount = require '../account'
       if 'string' is typeof to
         to = to.replace(/[^\w\s]/g, ' ').replace(/\s+/g, ' ').split(' ') # accept virtaully any non-wordchar delimiters for now.
       JAccount.all 'profile.nickname': $in: to, (err, recipients)->


sometimes the interfaces are already strings?

occurs in some (not all) classes in Java 7

--- src/disassembler.coffee
+++ src/disassembler.coffee
@@ -13,7 +13,8 @@
     privacy = (("#{flag} " if access_flags[flag]) for flag in ordered_flags).join ''

   source_file = _.find(class_file.attrs, (attr) -> attr.constructor.name == 'SourceFile')
-  ifaces = (class_file.constant_pool.get(i).deref().toExternalString() for i in class_file.interfaces).join ','
+  ifaces = (class_file.constant_pool.get(i).deref() for i in class_file.interfaces)
+  ifaces = ((if util.is_string(i) then i else i.toExternalString()) for i in ifaces).join ','
   rv = "Compiled from \"#{source_file.name}\"\n"
   rv += access_string class_file.access_flags
   if class_file.access_flags.interface


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/engine_clear.coffee
+++ src/engine_clear.coffee
@@ -15,6 +15,8 @@
     clear: false

   self.attrAccessor "clear", "backgroundColor"
+
+

   self.bind "beforeDraw", ->
     if I.clear


...restore state as currently opened if was opened

--- resources/assets/coffee/_classes/beatmap-pack.coffee
+++ resources/assets/coffee/_classes/beatmap-pack.coffee
@@ -26,16 +26,7 @@
     @packBody = @el.querySelector('.js-accordion__item-body')
     @expander = @el.querySelector('.js-accordion__item-header')
     @busy = false
-    @isCurrent = false
-
-    # hacky reset for turbolinks for now.
-    if $(@el).hasClass('js-accordion__item--expanded')
-      @nextFrame =>
-        @packBody.style.transition = 'none'
-        @packBody.style.height = '0'
-        @nextFrame =>
-          $(@el).removeClass('js-accordion__item--expanded')
-          @packBody.style.transition = ''
+    @isCurrent = @el.classList.contains('js-accordion__item--expanded')

     $('.js-accordion').on 'beatmappack:clicked', @onClick
     $(@expander).on 'click', (event) =>


Using VIM navigation to open slits too.

Using VIM navigation to open slits too.

--- keymaps/tree-view.cson
+++ keymaps/tree-view.cson
@@ -36,9 +36,13 @@
   'h': 'tree-view:collapse-directory'
   'enter': 'tree-view:open-selected-entry'
   'ctrl-right': 'tree-view:open-selected-entry-right'
+  'ctrl-l': 'tree-view:open-selected-entry-right'
   'ctrl-left': 'tree-view:open-selected-entry-left'
+  'ctrl-h': 'tree-view:open-selected-entry-left'
   'ctrl-up': 'tree-view:open-selected-entry-up'
+  'ctrl-k': 'tree-view:open-selected-entry-up'
   'ctrl-down': 'tree-view:open-selected-entry-down'
+  'ctrl-j': 'tree-view:open-selected-entry-down'
   'ctrl-1': 'tree-view:open-selected-entry-pane-1'
   'ctrl-2': 'tree-view:open-selected-entry-pane-2'
   'ctrl-3': 'tree-view:open-selected-entry-pane-3'


Fixed windows trunkation of files.

Fixed windows trunkation of files.

--- lib/coo/builder.coffee
+++ lib/coo/builder.coffee
@@ -1,305 +1,306 @@
-fs = require 'fs'
-path = require 'path'
-crypto = require 'crypto'
-XRegExp = require('xregexp').XRegExp
-mkdirp = require 'mkdirp'
-walkdir = require 'walkdir'
-_ = require 'underscore'
-util = require 'util'
-growl = require 'growl'
-# Compilers
-Coco = require('./compiler/coco').Coco
-CoffeeScript = require('./compiler/coffee-script').CoffeeScript
-Contracts = require('./compiler/contracts').Contracts
-IcedCoffeeScript = require('./compiler/iced-coffee-script').IcedCoffeeScript
-Kaffeine = require('./compiler/kaffeine').Kaffeine
-Move = require('./compiler/move').Move
-Sibilant = require('./compiler/sibilant').Sibilant
-Roy = require('./compiler/roy').Roy
-Haml = require('./compiler/haml').Haml
-Jade = require('./compiler/jade').Jade
-Markdown = require('./compiler/markdown').Markdown
-Less = require('./compiler/less').Less
-Stylus = require('./compiler/stylus').Stylus
-# Processors
-UglifyJs = require('./processor/uglify-js').UglifyJs
-CleanCss = require('./processor/clean-css').CleanCss
-
-generateCompilerConfig = (compiler, extension, target) ->
-  path: "**.#{extension}"
-  compiler: compiler
-  compile: true
-  targetFile: (file) ->
-    repRegex = new RegExp "(.#{target})?\\.#{extension}$"
-    if @compile then file.replace repRegex, ".#{target}" else file
-
-exports.Builder = class Builder
-  sourcePath: null
-  tmpPath: null
-  outputPath: null
-  compileDir: 'compiled'
-  srcHashDb: 'src-hash'
-  compileHashDb: 'compile-hash'
-  buildFileHashDb: 'build-file-hash'
-  copyFileHashDb: 'copy-file-hash'
-  defaultVersion: 'development'
-  compileConfigTree: {}
-  postConfigTree: {}
-  fileConfigTree: {}
-  # Versions are created in the output path, the first version is build default
-  versions:
-    development:
-      postProcess: false
-    production: {}
-  files: {}
-  compilePaths: [
-    {
-      # Default config
-      path: '**'
-      targetFile: (file) -> file
-    }
-    # JS compilers
-    generateCompilerConfig new CoffeeScript, 'coffee', 'js'
-    generateCompilerConfig new Contracts, 'contracts', 'js'
-    generateCompilerConfig new IcedCoffeeScript, 'iced', 'js'
-    generateCompilerConfig new Coco, 'co', 'js'
-    generateCompilerConfig new Kaffeine, 'k', 'js'
-    generateCompilerConfig new Move, 'mv', 'js'
-    generateCompilerConfig new Sibilant, 'sibilant', 'js'
-    generateCompilerConfig new Roy, 'roy', 'js'
-    # HTML compilers
-    generateCompilerConfig new Haml, 'haml', 'html'
-    generateCompilerConfig new Jade, 'jade', 'html'
-    generateCompilerConfig new Markdown, 'md', 'html'
-    # CSS compilers
-    generateCompilerConfig new Less, 'less', 'css'
-    generateCompilerConfig new Stylus, 'styl', 'css'
-  ]
-  postPaths: [
-    {
-      # Mangle and compress JS
-      path: '**.js'
-      process: true
-      processors: [
-        new UglifyJs
-      ]
-    }
-    {
-      # Compress CSS
-      path: '**.css'
-      process: true
-      processors: [
-        new CleanCss
-      ]
-    }
-  ]
-  # Load in a config file and run the config's 'config' function for this
-  loadConfig: (file) =>
-    require(file).config this
-  # Create a file builder given options
-  buildFile: (file, options) ->
-    @files[file] = options
-  # Set pre compile config for a path
-  compileConfig: (p, options) ->
-    options.path = p
-    @compilePaths.push options
-  # Set post compile config for a path
-  postConfig: (p, options) ->
-    options.path = p
-    @postPaths.push options
-  parseWildcardPath: (p) ->
-    escaped = p.split('*').map( (s) -> XRegExp.escape(s)).join '*'
-    converted = escaped.replace('**', '.*').replace /(.?)(.?)\*/g, ($0, $1, $2) ->
-      if $2 isnt '.' or $1 isnt '\\' then $0 else "#{$1}#{$2}[^/\\\\]*"
-    new RegExp "^#{converted}$"
-  # Check if a path (needle) matches another string, RegExp or array of paths
-  matchPath: (needle, haystack) ->
-    # Array case
-    if _.isArray haystack
-      for hs in haystack
-        return true if @matchPath needle, hs
-      return false
-    # Single case
-    haystack = @parseWildcardPath haystack unless _.isRegExp haystack
-    return haystack.test(needle)
-  # Build the source tree
-  setRootPath: (p) ->
-    @sourcePath = path.join p, 'src'
-    @tmpPath = path.join p, 'tmp'
-    @outputPath = path.join p, 'build'
-  build: (version) ->
-    version = @defaultVersion unless version?
-    throw "#{version} is not a valid version name" unless @versions[version]?
-    @compile version unless @versions[version].compile is false
-    @buildFiles version unless @versions[version].buildFiles is false
-    @postProcess version unless @versions[version].postProcess is false
-  compile: (version) ->
-    version = @defaultVersion unless version?
-    throw "#{version} is not a valid version name" unless @versions[version]?
-    # Create new files when required
-    targets = []
-    srcHashes = @loadDatabase version, @srcHashDb
-    for f, conf of @generateCompileConfigTree(version)
-      sourceFile = path.join @sourcePath, f
-      targetFile = path.join @getCompilePath(version), conf.targetFile(f)
-      targets.push targetFile unless conf.ignore is true
-      unless conf.ignore
-        srcHash = @getFileHash sourceFile
-        unless path.existsSync(targetFile) and srcHashes[sourceFile] is srcHash
-          mkdirp.sync path.dirname(targetFile)
-          if conf.compile
-            console.log "Compiling #{f}..."
-            source = fs.readFileSync sourceFile, 'utf8'
-            try
-              compiled = conf.compiler.compile(source)
-            catch e
-              title = "Error while compiling #{f}"
-              errorMesage = "#{title}\n#{e.toString()}"
-              console.log errorMesage
-              growl e.toString(),
-                title: title
-                name: 'coo'
-              continue
-            fs.writeFileSync targetFile, compiled
-          else
-            console.log "Copying #{f}..."
-            fs.writeFileSync targetFile, fs.readFileSync(sourceFile)
-          srcHashes[sourceFile] = srcHash
-          @saveDatabase version, @srcHashDb, srcHashes
-    # Prune orphaned files
-    buildFileNames = []
-    for bf in _.keys @files
-      buildFileNames.push path.join(@getCompilePath(version), bf)
-    walkdir.sync @getCompilePath(version), (f, stat) ->
-      return if stat.isDirectory()
-      if targets.indexOf(f) is -1 and buildFileNames.indexOf(f) is -1
-        console.log "Removing orphaned file #{f}..."
-        fs.unlinkSync f
-  buildFiles: (version) ->
-    version = @defaultVersion unless version?
-    throw "#{version} is not a valid version name" unless @versions[version]?
-    builtFiles = []
-    compiledFiles = []
-    cvPath = @getCompilePath version
-    buildFileHashes = @loadDatabase version, @buildFileHashDb
-    for c, f of @generateCompileConfigTree() when not f.ignore
-      compiledFiles.push f.targetFile(c)
-    for bf, bfConf of @files
-      # Check if we need update, searching sources one by one to get correct
-      # dependency order
-      bfPath = path.join cvPath, bf
-      sources = [bfConf.source] unless _.isArray bfConf.source
-      matchedFiles = []
-      matchedFileHashes = []
-      requiresBuild = false
-      for s in sources
-        for cf in compiledFiles when (matchedFiles.indexOf cf is -1) and
-        @matchPath cf, s
-          cfPath = path.join cvPath, cf
-          throw "Cannot find compiled file #{cf}" unless path.existsSync cfPath
-          matchedFiles.push cfPath
-          matchedFileHashes.push @getFileHash(cfPath)
-          builtFiles.push cf
-      buildFileExists = path.existsSync bfPath
-      if buildFileExists and matchedFiles.length is 0
-        console.log "Removing #{bf}..."
-        fs.unlinkSync bfPath
-      else if matchedFiles.length > 0
-        newMatchedFilesHash = @getHash matchedFileHashes.join()
-        unless path.existsSync(bfPath) and
-        buildFileHashes[bfPath] is newMatchedFilesHash
-          console.log "Building #{bf}..."
-          mkdirp.sync path.dirname(bfPath)
-          bfFile = fs.openSync bfPath, 'w'
-          for mf in matchedFiles
-            fs.writeSync(bfFile, fs.readFileSync(mf, 'utf8'), null)
-          buildFileHashes[bfPath] = newMatchedFilesHash
-          @saveDatabase version, @buildFileHashDb, buildFileHashes
-      compiledFiles.push bf
-    # Move files to build dir
-    copyFileHashes = @loadDatabase version, @copyFileHashDb
-    ovPath = path.join(@outputPath, version)
-    mkdirp.sync ovPath
-    buildFileNames = _.keys @files
-    walkdir.sync cvPath, (f, stat) =>
-      return if stat.isDirectory()
-      f = path.relative(cvPath, f)
-      outf = path.join ovPath, f
-      if builtFiles.indexOf(f) is -1 or buildFileNames.indexOf(f) isnt -1
-        compf = path.join cvPath, f
-        compfHash = @getFileHash compf
-        unless path.existsSync(outf) and
-        copyFileHashes[compf] is compfHash
-          # Copy
-          console.log "Outputting #{f}..."
-          mkdirp.sync path.dirname(outf)
-          fs.writeFileSync outf, fs.readFileSync(compf)
-          copyFileHashes[compf] = compfHash
-          @saveDatabase version, @copyFileHashDb, copyFileHashes
-      else if path.existsSync(outf)
-        console.log "Removing orphaned file #{f}..."
-        fs.unlinkSync outf
-    # Prune orphaned files
-    walkdir.sync ovPath, (f, stat) ->
-      return if stat.isDirectory()
-      relf = path.relative(ovPath, f)
-      unless path.existsSync(path.join(cvPath, relf))
-        console.log "Removing orphaned file #{f}..."
-        fs.unlinkSync f
-  postProcess: (version) ->
-    version = @defaultVersion unless version?
-    throw "#{version} is not a valid version name" unless @versions[version]?
-    return if @versions[version].postProcess is false
-    # Run processors
-    ovPath = path.join @outputPath, version
-    for f, c of @generatePostConfigTree(version) when not c.ignore
-      if c.process
-        p = path.join ovPath, f
-        source = fs.readFileSync p, 'utf8'
-        try
-          source = processor.process(source) for processor in c.processors
-        catch e
-          console.log "Error while processing #{f}:"
-          console.log e.toString()
-          continue
-        fs.writeFileSync p, source
-  generateCompileConfigTree: ->
-    @generateFileConfigTree @sourcePath, @compilePaths
-  generatePostConfigTree: (version) ->
-    @generateFileConfigTree path.join(@outputPath, version), @postPaths
-  generateFileConfigTree: (p, configs) ->
-    cJson = JSON.stringify configs
-    @fileConfigTree[p] = {} unless @fileConfigTree[p]?
-    @fileConfigTree[p][cJson] = {} unless @fileConfigTree[p][cJson]?
-    # Prune missing items from the tree
-    for f in _.keys @fileConfigTree[p][cJson]
-      abs = path.join @sourcePath, f
-      delete @fileConfigTree[p][cJson][f] unless path.existsSync abs
-    # Add new items to the tree
-    walkdir.sync p, (f, stat) =>
-      # Trim source dir off path
-      return if stat.isDirectory()
-      f = path.relative(p, f)
-      return if @fileConfigTree[p][cJson][f]?
-      for c in configs when @matchPath f, c.path
-        pathConf = @fileConfigTree[p][cJson][f] or {}
-        pathConf = _.defaults c, pathConf
-        @fileConfigTree[p][cJson][f] = pathConf
-    @fileConfigTree[p][cJson]
-  getHash: (data) ->
-    hash = crypto.createHash 'sha1'
-    hash.update data
-    hash.digest 'base64'
-  getFileHash: (file) -> @getHash fs.readFileSync(file, 'utf8')
-  getCompilePath: (version) -> path.join @tmpPath, version, @compileDir
-  getDatabaseFileName: (version, name) ->
-    "#{path.join(@tmpPath, version, name)}.json"
-  loadDatabase: (version, name) ->
-    file = @getDatabaseFileName version, name
-    return {} unless path.existsSync file
-    JSON.parse fs.readFileSync(file, 'utf8')
-  saveDatabase: (version, name, data) ->
-    file = @getDatabaseFileName version, name
-    fs.writeFileSync file, JSON.stringify(data)
-  # Link to top level, made accessible inside the object for configuration.
-  generateCompilerConfig: generateCompilerConfig
+fs = require 'fs'
+path = require 'path'
+crypto = require 'crypto'
+XRegExp = require('xregexp').XRegExp
+mkdirp = require 'mkdirp'
+walkdir = require 'walkdir'
+_ = require 'underscore'
+util = require 'util'
+growl = require 'growl'
+# Compilers
+Coco = require('./compiler/coco').Coco
+CoffeeScript = require('./compiler/coffee-script').CoffeeScript
+Contracts = require('./compiler/contracts').Contracts
+IcedCoffeeScript = require('./compiler/iced-coffee-script').IcedCoffeeScript
+Kaffeine = require('./compiler/kaffeine').Kaffeine
+Move = require('./compiler/move').Move
+Sibilant = require('./compiler/sibilant').Sibilant
+Roy = require('./compiler/roy').Roy
+Haml = require('./compiler/haml').Haml
+Jade = require('./compiler/jade').Jade
+Markdown = require('./compiler/markdown').Markdown
+Less = require('./compiler/less').Less
+Stylus = require('./compiler/stylus').Stylus
+# Processors
+UglifyJs = require('./processor/uglify-js').UglifyJs
+CleanCss = require('./processor/clean-css').CleanCss
+
+generateCompilerConfig = (compiler, extension, target) ->
+  path: "**.#{extension}"
+  compiler: compiler
+  compile: true
+  targetFile: (file) ->
+    repRegex = new RegExp "(.#{target})?\\.#{extension}$"
+    if @compile then file.replace repRegex, ".#{target}" else file
+
+exports.Builder = class Builder
+  sourcePath: null
+  tmpPath: null
+  outputPath: null
+  compileDir: 'compiled'
+  srcHashDb: 'src-hash'
+  compileHashDb: 'compile-hash'
+  buildFileHashDb: 'build-file-hash'
+  copyFileHashDb: 'copy-file-hash'
+  defaultVersion: 'development'
+  compileConfigTree: {}
+  postConfigTree: {}
+  fileConfigTree: {}
+  # Versions are created in the output path, the first version is build default
+  versions:
+    development:
+      postProcess: false
+    production: {}
+  files: {}
+  compilePaths: [
+    {
+      # Default config
+      path: '**'
+      targetFile: (file) -> file
+    }
+    # JS compilers
+    generateCompilerConfig new CoffeeScript, 'coffee', 'js'
+    generateCompilerConfig new Contracts, 'contracts', 'js'
+    generateCompilerConfig new IcedCoffeeScript, 'iced', 'js'
+    generateCompilerConfig new Coco, 'co', 'js'
+    generateCompilerConfig new Kaffeine, 'k', 'js'
+    generateCompilerConfig new Move, 'mv', 'js'
+    generateCompilerConfig new Sibilant, 'sibilant', 'js'
+    generateCompilerConfig new Roy, 'roy', 'js'
+    # HTML compilers
+    generateCompilerConfig new Haml, 'haml', 'html'
+    generateCompilerConfig new Jade, 'jade', 'html'
+    generateCompilerConfig new Markdown, 'md', 'html'
+    # CSS compilers
+    generateCompilerConfig new Less, 'less', 'css'
+    generateCompilerConfig new Stylus, 'styl', 'css'
+  ]
+  postPaths: [
+    {
+      # Mangle and compress JS
+      path: '**.js'
+      process: true
+      processors: [
+        new UglifyJs
+      ]
+    }
+    {
+      # Compress CSS
+      path: '**.css'
+      process: true
+      processors: [
+        new CleanCss
+      ]
+    }
+  ]
+  # Load in a config file and run the config's 'config' function for this
+  loadConfig: (file) =>
+    require(file).config this
+  # Create a file builder given options
+  buildFile: (file, options) ->
+    @files[file] = options
+  # Set pre compile config for a path
+  compileConfig: (p, options) ->
+    options.path = p
+    @compilePaths.push options
+  # Set post compile config for a path
+  postConfig: (p, options) ->
+    options.path = p
+    @postPaths.push options
+  parseWildcardPath: (p) ->
+    escaped = p.split('*').map( (s) -> XRegExp.escape(s)).join '*'
+    converted = escaped.replace('**', '.*').replace /(.?)(.?)\*/g, ($0, $1, $2) ->
+      if $2 isnt '.' or $1 isnt '\\' then $0 else "#{$1}#{$2}[^/\\\\]*"
+    new RegExp "^#{converted}$"
+  # Check if a path (needle) matches another string, RegExp or array of paths
+  matchPath: (needle, haystack) ->
+    # Array case
+    if _.isArray haystack
+      for hs in haystack
+        return true if @matchPath needle, hs
+      return false
+    # Single case
+    haystack = @parseWildcardPath haystack unless _.isRegExp haystack
+    return haystack.test(needle)
+  # Build the source tree
+  setRootPath: (p) ->
+    @sourcePath = path.join p, 'src'
+    @tmpPath = path.join p, 'tmp'
+    @outputPath = path.join p, 'build'
+  build: (version) ->
+    version = @defaultVersion unless version?
+    throw "#{version} is not a valid version name" unless @versions[version]?
+    @compile version unless @versions[version].compile is false
+    @buildFiles version unless @versions[version].buildFiles is false
+    @postProcess version unless @versions[version].postProcess is false
+  compile: (version) ->
+    version = @defaultVersion unless version?
+    throw "#{version} is not a valid version name" unless @versions[version]?
+    # Create new files when required
+    targets = []
+    srcHashes = @loadDatabase version, @srcHashDb
+    for f, conf of @generateCompileConfigTree(version)
+      sourceFile = path.join @sourcePath, f
+      targetFile = path.join @getCompilePath(version), conf.targetFile(f)
+      targets.push targetFile unless conf.ignore is true
+      unless conf.ignore
+        srcHash = @getFileHash sourceFile
+        unless path.existsSync(targetFile) and srcHashes[sourceFile] is srcHash
+          mkdirp.sync path.dirname(targetFile)
+          if conf.compile
+            console.log "Compiling #{f}..."
+            source = fs.readFileSync sourceFile, 'utf8'
+            try
+              compiled = conf.compiler.compile(source)
+            catch e
+              title = "Error while compiling #{f}"
+              errorMesage = "#{title}\n#{e.toString()}"
+              console.log errorMesage
+              growl e.toString(),
+                title: title
+                name: 'coo'
+              continue
+            fs.writeFileSync targetFile, compiled
+          else
+            console.log "Copying #{f}..."
+            fs.writeFileSync targetFile, fs.readFileSync(sourceFile)
+          srcHashes[sourceFile] = srcHash
+          @saveDatabase version, @srcHashDb, srcHashes
+    # Prune orphaned files
+    buildFileNames = []
+    for bf in _.keys @files
+      buildFileNames.push path.join(@getCompilePath(version), bf)
+    walkdir.sync @getCompilePath(version), (f, stat) ->
+      return if stat.isDirectory()
+      f = path.normalize(f)
+      if targets.indexOf(f) is -1 and buildFileNames.indexOf(f) is -1
+        console.log "Removing orphaned file #{f}..."
+        fs.unlinkSync f
+  buildFiles: (version) ->
+    version = @defaultVersion unless version?
+    throw "#{version} is not a valid version name" unless @versions[version]?
+    builtFiles = []
+    compiledFiles = []
+    cvPath = @getCompilePath version
+    buildFileHashes = @loadDatabase version, @buildFileHashDb
+    for c, f of @generateCompileConfigTree() when not f.ignore
+      compiledFiles.push f.targetFile(c)
+    for bf, bfConf of @files
+      # Check if we need update, searching sources one by one to get correct
+      # dependency order
+      bfPath = path.join cvPath, bf
+      sources = [bfConf.source] unless _.isArray bfConf.source
+      matchedFiles = []
+      matchedFileHashes = []
+      requiresBuild = false
+      for s in sources
+        for cf in compiledFiles when (matchedFiles.indexOf cf is -1) and
+        @matchPath cf, s
+          cfPath = path.join cvPath, cf
+          throw "Cannot find compiled file #{cf}" unless path.existsSync cfPath
+          matchedFiles.push cfPath
+          matchedFileHashes.push @getFileHash(cfPath)
+          builtFiles.push cf
+      buildFileExists = path.existsSync bfPath
+      if buildFileExists and matchedFiles.length is 0
+        console.log "Removing #{bf}..."
+        fs.unlinkSync bfPath
+      else if matchedFiles.length > 0
+        newMatchedFilesHash = @getHash matchedFileHashes.join()
+        unless path.existsSync(bfPath) and
+        buildFileHashes[bfPath] is newMatchedFilesHash
+          console.log "Building #{bf}..."
+          mkdirp.sync path.dirname(bfPath)
+          bfFile = fs.openSync bfPath, 'w'
+          for mf in matchedFiles
+            fs.writeSync(bfFile, fs.readFileSync(mf, 'utf8'), null)
+          buildFileHashes[bfPath] = newMatchedFilesHash
+          @saveDatabase version, @buildFileHashDb, buildFileHashes
+      compiledFiles.push bf
+    # Move files to build dir
+    copyFileHashes = @loadDatabase version, @copyFileHashDb
+    ovPath = path.join(@outputPath, version)
+    mkdirp.sync ovPath
+    buildFileNames = _.keys @files
+    walkdir.sync cvPath, (f, stat) =>
+      return if stat.isDirectory()
+      f = path.relative(cvPath, f)
+      outf = path.join ovPath, f
+      if builtFiles.indexOf(f) is -1 or buildFileNames.indexOf(f) isnt -1
+        compf = path.join cvPath, f
+        compfHash = @getFileHash compf
+        unless path.existsSync(outf) and
+        copyFileHashes[compf] is compfHash
+          # Copy
+          console.log "Outputting #{f}..."
+          mkdirp.sync path.dirname(outf)
+          fs.writeFileSync outf, fs.readFileSync(compf)
+          copyFileHashes[compf] = compfHash
+          @saveDatabase version, @copyFileHashDb, copyFileHashes
+      else if path.existsSync(outf)
+        console.log "Removing orphaned file #{f}..."
+        fs.unlinkSync outf
+    # Prune orphaned files
+    walkdir.sync ovPath, (f, stat) ->
+      return if stat.isDirectory()
+      relf = path.relative(ovPath, f)
+      unless path.existsSync(path.join(cvPath, relf))
+        console.log "Removing orphaned file #{f}..."
+        fs.unlinkSync f
+  postProcess: (version) ->
+    version = @defaultVersion unless version?
+    throw "#{version} is not a valid version name" unless @versions[version]?
+    return if @versions[version].postProcess is false
+    # Run processors
+    ovPath = path.join @outputPath, version
+    for f, c of @generatePostConfigTree(version) when not c.ignore
+      if c.process
+        p = path.join ovPath, f
+        source = fs.readFileSync p, 'utf8'
+        try
+          source = processor.process(source) for processor in c.processors
+        catch e
+          console.log "Error while processing #{f}:"
+          console.log e.toString()
+          continue
+        fs.writeFileSync p, source
+  generateCompileConfigTree: ->
+    @generateFileConfigTree @sourcePath, @compilePaths
+  generatePostConfigTree: (version) ->
+    @generateFileConfigTree path.join(@outputPath, version), @postPaths
+  generateFileConfigTree: (p, configs) ->
+    cJson = JSON.stringify configs
+    @fileConfigTree[p] = {} unless @fileConfigTree[p]?
+    @fileConfigTree[p][cJson] = {} unless @fileConfigTree[p][cJson]?
+    # Prune missing items from the tree
+    for f in _.keys @fileConfigTree[p][cJson]
+      abs = path.join @sourcePath, f
+      delete @fileConfigTree[p][cJson][f] unless path.existsSync abs
+    # Add new items to the tree
+    walkdir.sync p, (f, stat) =>
+      # Trim source dir off path
+      return if stat.isDirectory()
+      f = path.relative(p, f)
+      return if @fileConfigTree[p][cJson][f]?
+      for c in configs when @matchPath f, c.path
+        pathConf = @fileConfigTree[p][cJson][f] or {}
+        pathConf = _.defaults c, pathConf
+        @fileConfigTree[p][cJson][f] = pathConf
+    @fileConfigTree[p][cJson]
+  getHash: (data) ->
+    hash = crypto.createHash 'sha1'
+    hash.update data
+    hash.digest 'base64'
+  getFileHash: (file) -> @getHash fs.readFileSync(file, 'utf8')
+  getCompilePath: (version) -> path.join @tmpPath, version, @compileDir
+  getDatabaseFileName: (version, name) ->
+    "#{path.join(@tmpPath, version, name)}.json"
+  loadDatabase: (version, name) ->
+    file = @getDatabaseFileName version, name
+    return {} unless path.existsSync file
+    JSON.parse fs.readFileSync(file, 'utf8')
+  saveDatabase: (version, name, data) ->
+    file = @getDatabaseFileName version, name
+    fs.writeFileSync file, JSON.stringify(data)
+  # Link to top level, made accessible inside the object for configuration.
+  generateCompilerConfig: generateCompilerConfig


Don't fallback to cabal builder

Don't fallback to cabal builder

--- lib/ide-backend.coffee
+++ lib/ide-backend.coffee
@@ -20,7 +20,7 @@

     @buildTarget = state?.target ? {name: 'All'}
     @buildProject = state?.project ? {name: 'Auto'}
-    @buildBuilder = state?.builder ? 'cabal'
+    @buildBuilder = state?.builder

     @disposables.add @upi.addPanelControl @builderElem = (document.createElement 'ide-haskell-builder'),
       events:
@@ -65,6 +65,10 @@
       atom.project.getPaths()[0] ? process.cwd()

   cabalBuild: (cmd, opts) =>
+    unless @buildBuilder?
+      @setBuilder onComplete: => @cabalBuild(cmd, opts)
+      return
+
     # TODO: It shouldn't be possible to call this function until cabalProcess
     # exits. Otherwise, problems will ensue.

@@ -300,7 +304,7 @@
     @projectElem.innerText = "#{name}"

   showBuilder: ->
-    name = @buildBuilder ? "cabal"
+    name = @buildBuilder ? "Not set"
     @builderElem.innerText = "#{name}"

   setProject: ({onComplete}) ->


only load TermList when needed

only load TermList when needed

--- lib/intellinote.coffee
+++ lib/intellinote.coffee
@@ -9,7 +9,6 @@
 fs       = require 'fs'
 path     = require 'path'
 request  = require 'request'
-TermList = require 'term-list'

 TILDE = process.env.HOME || process.env.HOMEPATH || process.env.USERPROFILE
 CONFIG_FILE = path.join(TILDE,'.intellinote')
@@ -389,6 +388,7 @@
     unless choices?.length > 0
       callback(null,null)
     else
+      TermList = require 'term-list'
       console.log prompt
       # list = new TermList(({ marker: '\x1b[36m› \x1b[0m', markerLength: 2 }))
       list = new TermList()


:minibus: Linter::getLinter -> Linter::getEditorLinter

--- lib/linter-plus.coffee
+++ lib/linter-plus.coffee
@@ -25,7 +25,7 @@
       @activeEditor = editor
       # Exceptions thrown here prevent switching tabs
       try
-        @getLinter(editor)?.lint(false)
+        @getEditorLinter(editor)?.lint(false)
         @views.render()
       catch error
         atom.notifications.addError error.message, {detail: error.stack, dismissable: true}
@@ -39,9 +39,9 @@
         @_editorLinters.delete currentEditorLinter

   getActiveEditorLinter: ->
-    return @getLinter @activeEditor
+    return @getEditorLinter @activeEditor

-  getLinter: (editor) ->
+  getEditorLinter: (editor) ->
     return @_editorLinters.get editor

   eachEditorLinter: (callback) ->


:fire: Remove redundant Bubble creation

:fire: Remove redundant Bubble creation

--- lib/linter-plus.coffee
+++ lib/linter-plus.coffee
@@ -22,10 +22,7 @@
     @editorLinters = new Map
     @linters = []

-    if atom.config.get "editor.showErrorInline"
-      @bubble = new Bubble this
-
-    atom.config.observe 'linter.showErrorInline', (showErrorInline) =>
+    @subscriptions.add atom.config.observe 'linter.showErrorInline', (showErrorInline) =>
       if showErrorInline
         @bubble = new Bubble this
       else


updated before_save method to work like before_create

--- lib/minimongoid.coffee
+++ lib/minimongoid.coffee
@@ -197,10 +197,7 @@
     for k,v of attr
       @[k] = v

-    if @constructor.before_save
-      for k,v of @constructor.before_save(@)
-        attr[k] = v
-        @[k] = v
+    attr = @constructor.before_save(attr) if @constructor.before_save

     return @ if not @isValid()


AdminIntegrationsListView: Pass integration type to list item views.

--- client/admin/lib/views/integrations/adminintegrationslistview.coffee
+++ client/admin/lib/views/integrations/adminintegrationslistview.coffee
@@ -16,6 +16,8 @@
     options.cssClass = 'all-integrations'

     super options, data
+
+    @integrationType = @getOption 'integrationType'

     @createListController()
     @fetchIntegrations()
@@ -53,9 +55,12 @@
         { name: 'Twitter',         logo: 'https://koding-cdn.s3.amazonaws.com/temp-images/twitter.png',        desc: 'Social networking and microblogging service.'         }
       ]

+      data.length = 1  if @integrationType is 'configured' # dummy code.
+
       return @handleNoItem err  if err

       for item in data
+        item.integrationType = @integrationType
         listItem = @listController.addItem item
         @registerListItem listItem


catch up on coverage after refactoring

catch up on coverage after refactoring

--- test/phabs_commands_test.coffee
+++ test/phabs_commands_test.coffee
@@ -460,6 +460,40 @@
           expect(hubotResponse(1)).to.eql 'Task T24 created = http://example.com/T24'
           expect(hubotResponse(3)).to.eql 'T24 has status open, priority Low, owner user_with_phid'

+
+    context 'phab new proj3 a task', ->
+      beforeEach ->
+        room.robot.brain.data.phabricator.projects = {
+          'proj2': {
+            phid: 'PHID-PROJ-qhmexneudkt62wc7o3z4'
+          }
+        }
+        do nock.disableNetConnect
+        nock(process.env.PHABRICATOR_URL)
+          .get('/api/project.query')
+          .query({
+            'names[0]': 'project1',
+            'api.token': 'xxx'
+          })
+          .reply(200, { result: {
+            'data': [ ],
+            'slugMap': [ ],
+            'cursor': {
+              'limit': 100,
+              'after': null,
+              'before': null
+            }
+          } })
+
+      afterEach ->
+        room.robot.brain.data.phabricator = { }
+        nock.cleanAll()
+
+      context 'when project is unknown', ->
+        hubot 'phab new proj3 a task', 'user_with_phid'
+        it 'replies that project is unknown', ->
+          expect(hubotResponse()).to.eql 'Sorry, proj3 not found.'
+
   # ---------------------------------------------------------------------------------
   context 'someone creates a new paste', ->
     context 'something goes wrong', ->
@@ -490,6 +524,11 @@
       afterEach ->
         nock.cleanAll()

+      context 'when user is doing it for the first time and has no email recorded', ->
+        hubot 'ph paste a new paste'
+        it 'invites the user to set his email address', ->
+          expect(hubotResponse()).to.eql "Sorry, I can't figure out your email address :( " +
+                                         'Can you tell me with `.phab me as you@yourdomain.com`?'
       context 'ph paste a new paste', ->
         hubot 'ph paste a new paste', 'user_with_phid'
         it 'gives the link to fill up the paste Paste', ->
@@ -545,6 +584,39 @@
         hubot 'phab count proj1', 'user_with_phid'
         it 'replies with the unmber of tasks in the project', ->
           expect(hubotResponse()).to.eql 'proj1 has no tasks.'
+
+    context 'phab count proj3', ->
+      beforeEach ->
+        room.robot.brain.data.phabricator.projects = {
+          'proj2': {
+            phid: 'PHID-PROJ-qhmexneudkt62wc7o3z4'
+          }
+        }
+        do nock.disableNetConnect
+        nock(process.env.PHABRICATOR_URL)
+          .get('/api/project.query')
+          .query({
+            'names[0]': 'project1',
+            'api.token': 'xxx'
+          })
+          .reply(200, { result: {
+            'data': [ ],
+            'slugMap': [ ],
+            'cursor': {
+              'limit': 100,
+              'after': null,
+              'before': null
+            }
+          } })
+
+      afterEach ->
+        room.robot.brain.data.phabricator = { }
+        nock.cleanAll()
+
+      context 'when project is unknown', ->
+        hubot 'phab count proj3', 'user_with_phid'
+        it 'replies that project is unknown', ->
+          expect(hubotResponse()).to.eql 'Sorry, proj3 not found.'

   # ---------------------------------------------------------------------------------
   context 'user changes status for a task', ->
@@ -1254,6 +1326,39 @@
           expect(hubotResponse()).to.eql "There is no task matching 'gitlab' in project 'proj3'."
           expect(hubotResponseCount()).to.eql 1

+    context 'phab count proj4', ->
+      beforeEach ->
+        room.robot.brain.data.phabricator.projects = {
+          'proj2': {
+            phid: 'PHID-PROJ-qhmexneudkt62wc7o3z4'
+          }
+        }
+        do nock.disableNetConnect
+        nock(process.env.PHABRICATOR_URL)
+          .get('/api/project.query')
+          .query({
+            'names[0]': 'project1',
+            'api.token': 'xxx'
+          })
+          .reply(200, { result: {
+            'data': [ ],
+            'slugMap': [ ],
+            'cursor': {
+              'limit': 100,
+              'after': null,
+              'before': null
+            }
+          } })
+
+      afterEach ->
+        room.robot.brain.data.phabricator = { }
+        nock.cleanAll()
+
+      context 'when project is unknown', ->
+        hubot 'phab proj4 gitlab', 'user_with_phid'
+        it 'replies that project is unknown', ->
+          expect(hubotResponse()).to.eql 'Sorry, proj4 not found.'
+
   # ---------------------------------------------------------------------------------
   context 'permissions system', ->
     beforeEach ->


KDRouter: remove dead code

KDRouter: remove dead code

--- client/Framework/core/KDRouter.coffee
+++ client/Framework/core/KDRouter.coffee
@@ -94,6 +94,7 @@
     @addRoute route, listener  for own route, listener of routes

   handleRoute:(userRoute, options={})->
+
     [frag, query...] = (userRoute ? @getDefaultRoute?() ? '/').split '?'

     query = @utils.parseQuery query.join '&'
@@ -105,8 +106,6 @@

     node = @tree
     params = {}
-
-    isRooted = '/' is frag[0]

     frag = frag.split '/'
     frag.shift() # first edge is garbage like '' or '#!'


Revert "Activity: tickerbox temporarily disabled"

This reverts commit 43a162854f77fd9287ce34de997ce23560b2ccf9.

--- client/Social/Activity/AppView.coffee
+++ client/Social/Activity/AppView.coffee
@@ -27,6 +27,7 @@
     @referalBox       = new ReferalBox
     @topicsBox        = new ActiveTopics
     @usersBox         = new ActiveUsers
+    @tickerBox        = new ActivityTicker

     @mainBlock        = new KDCustomHTMLView tagName : "main" #"activity-left-block"
     @sideBlock        = new KDCustomHTMLView tagName : "aside"   #"activity-right-block"


code styling for makeReply function

code styling for makeReply function

--- client/app/utils/message_utils.coffee
+++ client/app/utils/message_utils.coffee
@@ -75,9 +75,10 @@
             message.references = message.references.concat message.inReplyTo

         switch action
+
             when ComposeActions.REPLY
-                separator = t('compose reply separator',
-                    {date: dateHuman, sender: sender})
+                params = date: dateHuman, sender: sender
+                separator = t 'compose reply separator', params
                 message.to = @getReplyToAddress inReplyTo
                 message.cc = []
                 message.bcc = []
@@ -90,10 +91,12 @@

#{html}

                     """
+
             when ComposeActions.REPLY_ALL
-                separator = t('compose reply separator',
-                    {date: dateHuman, sender: sender})
+                params = date: dateHuman, sender: sender
+                separator = t 'compose reply separator', params
                 message.to = @getReplyToAddress inReplyTo
+
                 # filter to don't have same address twice
                 toAddresses = message.to.map (dest) -> return dest.address
                 message.cc = [].concat(inReplyTo.get('from'),
@@ -110,6 +113,7 @@

#{html}

                     """
+
             when ComposeActions.FORWARD
                 separator = t('compose forward separator',
                     {date: dateHuman, sender: sender})


Add textOverflow tests

Add textOverflow tests

--- test/tests/TextLayerTest.coffee
+++ test/tests/TextLayerTest.coffee
@@ -237,3 +237,36 @@
 				fontFamily: Utils.loadWebFont("Raleway", 800)
 			l.fontFamily.should.equal "Raleway"
 			expect(l.fontWeight).to.equal 400
+
+	describe "textOverflow", ->
+		it "should enable clipping", ->
+			l = new TextLayer
+				textOverflow: "ellipsis"
+			l.clip.should.equal true
+
+		it "should enable multilineOverflow setting a specific height", ->
+			l = new TextLayer
+				textOverflow: "ellipsis"
+				height: 20
+			l.multiLineOverflow.should.equal true
+
+		it "should should disable multilineOverflow when using clipping and setting a specific height", ->
+			l = new TextLayer
+				textOverflow: "clip"
+				height: 20
+			l.multiLineOverflow.should.equal false
+
+		it "should set whitespace to nowrap setting no height", ->
+			l = new TextLayer
+				textOverflow: "ellipsis"
+			l.whiteSpace.should.equal "nowrap"
+			l.multiLineOverflow.should.equal false
+
+		it "should disable properties when it's disabled", ->
+			l = new TextLayer
+				textOverflow: "ellipsis"
+			l.textOverflow = null
+			l.clip.should.equal false
+			expect(l.whitespace).to.equal undefined
+			l.multiLineOverflow.should.equal false
+


default collection from props

default collection from props

--- client/skr/components/UOMChooser.cjsx
+++ client/skr/components/UOMChooser.cjsx
@@ -7,8 +7,8 @@
         label: 'UOM'

     render: ->
-        props = if (not @props.model.uom_choices or @props.model.uom_choices.isEmpty()) then {}
-        else {collection: @props.model.uom_choices}
+        props = _.clone(@props)
+        props.collection ||= @props.model.uom_choices
         <LC.SelectField
             key="uom"
             displayFallback={@props.model.uom.combined}


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/flickerable.coffee
+++ src/flickerable.coffee
@@ -54,8 +54,12 @@
   @param {Number} [frequency=3] The number of frames in between opacity changes
   @param {Number} [alpha=0.5] The alpha value to flicker to
   ###
-  flicker: ({duration, frequency, alpha}={}) ->
-    I.flickerDuration = duration if duration?
-    I.flickerFrequency = frequency if frequency?
-    I.flickerAlpha = alpha if alpha?
-
+  flicker: (options={}) ->
+    Object.reverseMerge options,
+      duration: 2
+      frequency:0.1
+      alpha: 0.5
+
+    I.flickerDuration = options.duration
+    I.flickerFrequency = options.frequency
+    I.flickerAlpha = options.alpha


Thoughts on parsing.

Thoughts on parsing.

--- src/lib/timezone.coffee
+++ src/lib/timezone.coffee
@@ -190,7 +190,73 @@

   isDate = (object) ->

+  # Parse a pattern, possibly fuzzy.
   parse = (pattern) ->
+    # Best foot forward, an ISO date. An ISO date can also be YYYY, but we catch
+    # that case later on, so we don't pluck YYYY/MM or some such now.
+    if match = ///
+      (.*)
+      (?:
+        (\d\d\d\d)-(\d\d)(?:-(\d\d))?
+        |
+        (\d{4})(\d{2})(\d{2})
+      )
+      (?:\s+|T)
+      (?:
+        (\d\d)(?::?(\d\d))?(?::?(\d\d))?
+        (?:.(\d+))?
+      )?
+      (?:
+        (?:\s+|Z)
+        (?:([+-])(\d{2})(?::?(\d{2}))?)
+      )
+      (.*)
+    ///.exec(pattern)
+      before = match.splice(0, 2).pop()
+
+      date = match.splice(0, 3)
+      [ year, month, day ] = date if date[0]?
+
+      date = match.splice(0, 3)
+      [ year, month, day ] = date if date[0]?
+
+      time = match.splice(0, 4)
+      [ hours, minutes, seconds, milliseconds ] = time if time[0]?
+
+      zone = match.splice(0, 3)
+      [ sign, zoneHours, zoneMinutes ] = time if time[0]?
+
+      after = match.pop()
+
+      remaining = (before + after).replace(/\s+/, "").length
+
+      if remaining is 0
+        return makeDate year, month, day, hours, minutes, seconds, milliseconds
+
+    # Parse a UNIX date.
+
+    # Try to find something date like, interpret using locale.
+
+      # Take the locale preference for month/day ordering.
+
+      # If it doesn't fit, try the other way.
+
+      # If that doesn't fit, see if you can find a date in the remaining bit.
+
+    # Look for a time, either in the whole pattern, or in what's left after the
+    # date consumed a date-like thing.
+
+    # Now let's split what we've got and look for locale specific words that are
+    # meaningful.
+
+    # If we're willing to be fuzzy, then we'll look harder. Maybe we have a
+    # bunch of regular expressions to run, that will extract locale specific
+    # strings, say, this looks like an hour, this looks like a day of the week,
+    # this looks like a date. So /at (\d+)\s*([pa]m?)/i or some such, with ()
+    # for place holders for bits of the pattern that won't match.
+
+    # Let's start by parsing ISO, UNIX and not very fuzzy dates. Really, you can
+    # just tell the user to try harder, or else prompt with a date format.

   offsetInMilliseconds = (pattern) ->
     match = /^(-?)(\d)+:(\d)+(?::(\d+))?$/.exec(pattern).slice(1)


update selectedTickets on add, addarray

update selectedTickets on add, addarray

--- coffee/services/ticket.coffee
+++ coffee/services/ticket.coffee
@@ -170,8 +170,6 @@
       if ticket.show is SHOW.NOT then return
       selectableTickets.push ticket
       selectableTickets.sortById()
-      if selectedTickets.length is 0
-        selectedTickets.push ticket

   return {
@@ -222,6 +220,10 @@
     ###
     add: (ticket) ->
       _add(ticket)
+      if selectedTickets.length is 0
+        selectedTickets.push ticket
+      if not selectedTickets[0]?
+        selectedTickets[0] = ticket
       _setLocal()

@@ -232,6 +234,10 @@
       console.log 'tikcet addArray'
       if not arr? then return
       for t in arr then _add t
+      if selectedTickets.length is 0
+        selectedTickets.push selectableTickets[0]
+      if not selectedTickets[0]?
+        selectedTickets[0] = selectableTickets[0]
       _setLocal()


Kenkirk fix

Kenkirk fix

I'm an idiot and I added Commander Kenkirk rank translation with the ship tang instead the name tag. Fixed.

--- coffeescripts/cards-es.coffee
+++ coffeescripts/cards-es.coffee
@@ -522,7 +522,7 @@
             ship: "VT-49 Diezmador"
         "Commander Kenkirk":
             ship: "VT-49 Diezmador"
-            ship: "Comandante Kenkirk"
+            name: "Comandante Kenkirk"
             text: """Si no te quedan escudos y tienes asignada al menos 1 carta de Daño, tu Agilidad aumenta en 1."""
         "Captain Oicunn":
             name: "Capitán Oicunn"


BF-130 adding other planes

BF-130 adding other planes

--- app/assets/javascripts/view.coffee
+++ app/assets/javascripts/view.coffee
@@ -144,7 +144,7 @@
         @changePrev VIEW_3D
         @first = false

-      for dimension in [PLANE_XY]
+      for dimension in [PLANE_XY, PLANE_YZ, PLANE_XZ]
         for kind in [0..1]
           #i++
           offsets = cam2d.getOffsets dimension


api notification wrappers.

api notification wrappers.

--- picoCTF-web/web/coffee/dependencies.coffee
+++ picoCTF-web/web/coffee/dependencies.coffee
@@ -10,17 +10,19 @@
       when 0
         window.location.href = "/login"

+getStyle = (data) ->
+  style = "info"
+  switch data.status
+    when 0
+      style = "error"
+    when 1
+      style = "success"
+  return style
+
 window.apiNotify = (data) ->
-  style = "info"
-  if data.status == 0
-    style = "error"
-
+  style = getStyle data
   $.notify data.message, style

 $.fn.apiNotify = (data, configuration) ->
-  style = "info"
-  if data.status == 0
-    style = "error"
-  configuration["className"] = style
-
-  return $(this).notify(message, configuration)
+  configuration["className"] = getStyle data
+  return $(this).notify(data.message, configuration)


Added redirect argument for delayed redirection

--- picoCTF-web/web/coffee/dependencies.coffee
+++ picoCTF-web/web/coffee/dependencies.coffee
@@ -28,9 +28,14 @@
       style = "success"
   return style

-@apiNotify = (data) ->
+@apiNotify = (data, redirect) ->
   style = getStyle data
   $.notify data.message, style
+
+  if redirect and data.status is 1
+    setTimeout (->
+        window.location = redirect
+      ), 1000

 @numericalSort = (data) ->
   data.sort (a, b) ->


remove unused parameter

remove unused parameter

--- services/spelling/app/coffee/ASpellWorkerPool.coffee
+++ services/spelling/app/coffee/ASpellWorkerPool.coffee
@@ -11,7 +11,6 @@

 	constructor: (@options) ->
 		@PROCESS_POOL = []
-		@timeout = 1000

 	create: (language) ->
 		if @PROCESS_POOL.length >= @MAX_WORKERS


Fix issue #114

Fix issue #114

--- app/controllers/interactive.coffee
+++ app/controllers/interactive.coffee
@@ -14,6 +14,7 @@
     @groupsList = new Array
     User.bind 'sign-in', @render
     User.bind 'sign-in', @activeGroups
+    User.bind 'sign-in', @checkCurrentGroup
     UserGroup.bind 'participate', @setGroup
     UserGroup.bind 'stop', @reset
     UserGroup.bind 'create', @addGroup
@@ -110,5 +111,12 @@
   toggleDropdown: =>
     @groupsDropdown.toggleClass 'show-dropdown'

+  checkCurrentGroup: =>
+    console.log 'here'
+    if User.current?.user_group_id is UserGroup.current?.id
+      return
+    else
+      delete @group
+      delete UserGroup.current

 module.exports = Interactive


add comment about ZPOPMIN

add comment about ZPOPMIN

--- app/coffee/RedisManager.coffee
+++ app/coffee/RedisManager.coffee
@@ -305,6 +305,7 @@
 			return callback() if !reply?.length # return if no projects ready to be processed
 			# pop the oldest entry (get and remove in a multi)
 			multi = rclient.multi()
+			# Poor man's version of ZPOPMIN, which is only available in Redis 5.
 			multi.zrange keys.flushAndDeleteQueue(), 0, 0, "WITHSCORES"
 			multi.zremrangebyrank keys.flushAndDeleteQueue(), 0, 0
 			multi.zcard keys.flushAndDeleteQueue() # the total length of the queue (for metrics)


force next url register form

force next url register form

--- app/coffee/modules/auth.coffee
+++ app/coffee/modules/auth.coffee
@@ -315,7 +315,7 @@
 ## Register Directive
 #############################################################################

-RegisterDirective = ($auth, $confirm, $location, $navUrls, $config, $routeParams, $analytics, $translate) ->
+RegisterDirective = ($auth, $confirm, $location, $navUrls, $config, $routeParams, $analytics, $translate, $window) ->
     link = ($scope, $el, $attrs) ->
         if not $config.get("publicRegisterEnabled")
             $location.path($navUrls.resolve("not-found"))
@@ -324,12 +324,20 @@
         $scope.data = {}
         form = $el.find("form").checksley({onlyOneErrorElement: true})

-        $scope.nextUrl = $navUrls.resolve("home")
+        console.log $routeParams['forceNext']
+
+        if $routeParams['forceNext'] and $routeParams['forceNext'] != $navUrls.resolve("register")
+            $scope.nextUrl = decodeURIComponent($routeParams['forceNext'])
+        else
+            $scope.nextUrl = $navUrls.resolve("home")

         onSuccessSubmit = (response) ->
             $analytics.trackEvent("auth", "register", "user registration", 1)

-            $location.url($scope.nextUrl)
+            if $scope.nextUrl.indexOf('http') == 0
+                $window.location.href = $scope.nextUrl
+            else
+                $location.url($scope.nextUrl)

         onErrorSubmit = (response) ->
             if response.data._error_message
@@ -357,7 +365,7 @@
     return {link:link}

 module.directive("tgRegister", ["$tgAuth", "$tgConfirm", "$tgLocation", "$tgNavUrls", "$tgConfig",
-                                "$routeParams", "$tgAnalytics", "$translate", RegisterDirective])
+                                "$routeParams", "$tgAnalytics", "$translate", "$window", RegisterDirective])

 #############################################################################


Remove (awfull) hack and enable submit on press enter key on login.

Removed hack does not works. Also, it causes redirects to ugly 405 page
of nginx.

--- app/coffee/modules/auth.coffee
+++ app/coffee/modules/auth.coffee
@@ -153,21 +153,16 @@
         $scope.data = {}

         onSuccessSubmit = (response) ->
-            # NOTE: Hack to remember the login form values
-            form = $el.find("form")
-
-            form.attr("method", "POST")
             if $routeParams['next'] and $routeParams['next'] != $navUrls.resolve("login")
-                form.attr("action", $routeParams['next'])
+                nextUrl = $routeParams['next']
             else
-                form.attr("action", $navUrls.resolve("home"))
-
-            form.submit()
+                nextUrl = $navUrls.resolve("home")
+
+            $location.path(nextUrl)

         onErrorSubmit = (response) ->
             $confirm.notify("light-error", "According to our Oompa Loompas, your username/email
                                             or password are incorrect.") #TODO: i18n
-
         submit = ->
             form = $el.find("form").checksley()
             if not form.validate()
@@ -177,6 +172,10 @@
             promise.then(onSuccessSubmit, onErrorSubmit)

         $el.on "click", "a.button-login", (event) ->
+            event.preventDefault()
+            submit()
+
+        $el.on "submit", "form", (event) ->
             event.preventDefault()
             submit()


[account] removed load indicator

[account] removed load indicator

--- app/js/controllers/transfer.coffee
+++ app/js/controllers/transfer.coffee
@@ -63,7 +63,7 @@
                 else
                     $scope.no_account = true

-    $scope.showLoadingIndicator(refresh_accounts_promise)
+    #$scope.showLoadingIndicator(refresh_accounts_promise)

     Blockchain.get_info().then (config) ->
         $scope.memo_size_max = config.memo_size_max


pushing my fix

pushing my fix

--- app/lib/surface/Surface.coffee
+++ app/lib/surface/Surface.coffee
@@ -52,7 +52,7 @@
     coords: null  # use world defaults, or set to false/true to override
     playJingle: false
     showInvisible: false
-    frameRate: 60  # Best as a divisor of 60, like 15, 30, 60, with RAF_SYNCHED timing.
+    frameRate: 30  # Best as a divisor of 60, like 15, 30, 60, with RAF_SYNCHED timing.

   subscriptions:
     'level:disable-controls': 'onDisableControls'


Revert "Nginx: redirect www to non-www"

Revert "Nginx: redirect www to non-www"

--- deployment/nginx.coffee
+++ deployment/nginx.coffee
@@ -209,11 +209,6 @@
       return 301 https://$host$request_uri;
     }

-    # redirect www to non-www
-    server {
-      server_name ~^(www\.)?(?<domain>.+)$;
-      return 301 https://$domain$request_uri;
-    }

     # start server
     server {


Emit an 'ip' event

Emit an 'ip' event

--- src/lib/Network.coffee
+++ src/lib/Network.coffee
@@ -328,11 +328,19 @@
     node.component.network.on 'data', (data) -> emitSub 'data', data
     node.component.network.on 'endgroup', (data) -> emitSub 'endgroup', data
     node.component.network.on 'disconnect', (data) -> emitSub 'disconnect', data
+    node.component.network.on 'ip', (data) -> emitSub 'ip', data
     node.component.network.on 'process-error', (data) ->
       emitSub 'process-error', data

   # Subscribe to events from all connected sockets and re-emit them
   subscribeSocket: (socket, source) ->
+    socket.on 'ip', (ip) =>
+      @emit 'ip',
+        id: socket.getId true
+        type: ip.type
+        socket: socket
+        data: ip.data
+        metadata: socket.metadata
     socket.on 'connect', =>
       if source and source.component.isLegacy()
         # Handle activation for legacy components


bug fix

bug fix

--- src/lib/faction.coffee
+++ src/lib/faction.coffee
@@ -168,8 +168,10 @@
         lat = Config.Faction.Center.Lat + Math.random() * 0.2 - 0.1
         lng = Config.Faction.Center.Lng + Math.random() * 0.2 - 0.1

+        message = message.toString()
+
         data =
-            messageSendPlext: message.toString()
+            messageSendPlext: message
             latE6SendPlext: Math.round(lat * 1e6)
             lngE6SendPlext: Math.round(lng * 1e6)
             chatTabSendPlext: 'faction'
@@ -185,10 +187,10 @@

             afterResponse: (callback) ->

-                if data.message.length > 30
-                    thMsg = data.message.substr(0, 30) + '...'
+                if message.length > 30
+                    thMsg = message.substr(0, 30) + '...'
                 else
-                    thMsg = data.message
+                    thMsg = message

                 logger.info "[Get] #{received}" if received?
                 logger.info "[Send] #{thMsg}"


allow disable chat

allow disable chat

--- src/lib/faction.coffee
+++ src/lib/faction.coffee
@@ -60,6 +60,8 @@

 parseData = (callback) ->

+    return callback() if argv.chat is 'false'
+
     storage.lastParsedTime = Date.now() - Config.Faction.MaxParseTimespan if storage.lastParsedTime is null

     Database.db.collection('Chat.Faction').find({time: {$gte: storage.lastParsedTime}}).sort {time: 1}, (err, cursor) ->


Update collation Docco.

Update collation Docco.

--- src/lib/strata_.coffee
+++ src/lib/strata_.coffee
@@ -22,7 +22,8 @@
 #
 # Strata is **persistent**. It stores your tree in page files. The page files
 # are plain old JSON, text files that are easy to manage. You can view them with
-# `tail -f`, back them up hot using `rsync`, and version them with `git`.
+# `less` or `tail -f`, back them up hot using `rsync`, and version them with
+# `git`.
 #
 # Strata is **durable**. It only appends records to to file, so a hard shutdown
 # will only ever lose the few last records added. Pages are journaled when they
@@ -31,39 +32,56 @@
 # Strata is a b‑tree. A b‑tree is a database primiative. Using
 # Strata, you can start to experiment with database designs of your own. You can
 # use Strata to build an MVCC database table, like PostgreSQL. You can create
-# Strata b‑trees to create relationship indexes. You can use Strata to
-# store what ever form of JSON suits your needs like NoSQL databases.
-#
-# Strata is two database primatives in one, because with a time series index,
-# you can use Strata as a write ahead-log.
+# Strata b‑trees to create indexes into structured data that is not
+# already in a database, like monster log files. You can use Strata to store
+# your data in what ever form of JSON suits you like a NoSQL database.
+#
+# As a bonus, Strata is two database primatives in one, because with a time
+# series index, you can use Strata as a write‑ahead log.
 #
 # Strata runs anywhere that Node.js runs, in pure JavaScript.
 #
-# TK Transition.
+# ## Collaboration
+#
+# Documentation for Strata is presented here, in literate programming style. API
+# documentation is not yet available, so scoot down to the `Strata` class for an
+# in-depth look at the API. Have a look at the extensive test suite for examples
+# on the different operations.
+#
+# If you find a bug with Strata, please report it at the [GitHub
+# Issues](https://github.com/bigeasy/strata/issues). If you want to leave a
+# comment on the documentation or the code, you can ping me, Alan Gutierrez, at
+# @[bigeasy](https://twitter.com/#!/bigeasy) on Twitter.
+#
+# Feel free to fork and explore. Note that Strata is a database primitive, not a
+# database in itself. Before you fork and add what you feel are missing
+# features, please consult with me. Perhaps your ideas are better expressed as
+# project that employs Strata, intead of to a patch to Strata itself.
 #
 # ## Terminology
 #
-# We refer to the nodes in our b‑tree as *pages*. The term node conjures an
-# image of a discrete component in a linked data structure that contains one,
-# maybe two or three, values. Nodes in a b‑tree contain hundreds or thousands of
-# values. They are indexed. They are read from disk. They are allowed to fall
-# out of memory when they have not been recently referenced. These are behaviors
-# that conjure an image of a page of values.
-#
-# Otherwise, we use common teriminology for *height*, *depth*, *parent*,
-# *child*, *split* and *merge*.
+# We refer to the nodes in our b‑tree as ***pages***. The term node
+# conjures an image of a discrete component in a linked data structure that
+# contains one, maybe two or three, values. Nodes in a b‑tree contain
+# hundreds or thousands of values. They are indexed. They are read from disk.
+# They are allowed to fall out of memory when they have not been recently
+# referenced. These are behaviors that people associate with a page of values.
+#
+# Otherwise, we use common terminology for ***height***, ***depth***,
+# ***parent***, ***child***, ***split*** and ***merge***.
 #
 # There is no hard and fast definition for all the terms. A leaf is a fuzzy
 # concept in b‑tree literature, for example. We call a page that contains
-# records a *leaf page*. We call a non-leaf page a *branch page*. The term
-# *order* means different things to different people.  We define the order of a
-# branch page to be the maximum number of child pages, while the order of a leaf
-# page to be the maximum number of records.
-#
-# The term *b‑tree* itself may not be correct. There are different names for
-# b‑tree that reflect the variations of implementation, but those distinctions
-# have blurred over the years. Our implemenation may be considered a b+tree,
-# since pages are linked, and records are stored only in the leaves.
+# records a ***leaf page***. We call a non-leaf page a ***branch page***. The
+# term ***order*** means different things to different people.  We define the
+# order of a branch page to be the maximum number of child pages, while the
+# order of a leaf page to be the maximum number of records.
+#
+# The term ***b‑tree*** itself may not be correct. There are different
+# names for b‑tree that reflect the variations of implementation, but
+# those distinctions have blurred over the years. Our implemenation may be
+# considered a b+tree, since pages are linked, and records are stored only in
+# the leaves.
 #
 # Terms specific to our implementation will be introduced as they are
 # encountered in the document.
@@ -71,14 +89,12 @@
 # ## What is a b‑tree?
 #
 # This documentation assumes that you understand the theory behind the
-# b‑tree,
-# and know the variations of implementation. If you are interested in learning
-# about b‑trees you should start with the Wikipedia articles on
-# [B-trees](http://en.wikipedia.org/wiki/B-tree) and
+# b‑tree, and know the variations of implementation. If you are
+# interested in learning about b‑trees you should start with the
+# Wikipedia articles on [B-trees](http://en.wikipedia.org/wiki/B-tree) and
 # [B+trees](http://en.wikipedia.org/wiki/B%2B_tree). I was introduced to
-# b‑trees
-# while reading [Algorithms in C](http://www.amazon.com/dp/0201314525), quite
-# some time ago.
+# b‑trees while reading [Algorithms in
+# C](http://www.amazon.com/dp/0201314525), quite some time ago.
 #
 # ## What flavor of b‑tree is this?
 #
@@ -90,9 +106,9 @@
 # Branch pages contain links to other pages, and do not store records
 # themselves.
 #
-# Leaf pages are linked in ascending order for traversal by cursors. Branch
-# pages are singly linked in ascending order to simplify implementation of
-# branch page merges.
+# Leaf pages are linked in ascending order to simply the implementatoin of
+# traversal by cursors. Branch pages are singly linked in ascending order to
+# simplify implementation of branch page merges.
 #
 # The order of a branch page is the maximum number of children for a branch
 # page. The order of a leaf page is maximum number of records for a leaf page.
@@ -100,10 +116,11 @@
 # sibling pages next to each other can be combined to create a page less than
 # than the order they are merged.
 #
-# The b‑tree always has a root branch page. The order of the tree increases when
-# the root branch page is split. It decreases when the root branch page is
-# merged. The split of the root branch is a different operation from the split
-# of a non-root branch, because the root branch does not have siblings.
+# The b‑tree always has a root branch page. The height of the tree
+# increases when the root branch page is split. It decreases when the root
+# branch page is merged. The split of the root branch is a different operation
+# from the split of a non-root branch, because the root branch does not have
+# siblings.

 # Requried node.js libraries.
 fs = require "fs"
@@ -113,7 +130,7 @@
   to[key] = value for key, value of from
   to

-# Useful for debugging. If you don't see them called in the code, it means the
+# Used for debugging. If you don't see them called in the code, it means the
 # code is absolutely bug free.
 die = (splat...) ->
   console.log.apply null, splat if splat.length
@@ -128,24 +145,26 @@
 # comparator. The extractor is used to extract a key from the stored record. The
 # comparator is used to order records by comparing the key.
 #
-# Separating extraction from comparison allows us to cache the key.
-#
-# Keys are relevant even when the record itself is not of interest to the
-# application developer. Keys are used to order the tree, so when we will
-# constantly be reading records off the disk solely to get their key value.  If
-# we don't need the actual record, it will eventually be collected by a cache
-# purge, while the key will be retained. If the key is subset of a large record,
-# purging the records and retaining the keys will reduce the in memory size of
-# the b‑tree.
-#
-# Also, the comparator is pretty easily generalized, while the exractor
-# is invariably specialized. You might have a single string comparator that you
-# use with many different extractors.
+# Separating extraction from comparison allows us to cache the key. We do not
+# need the whole record for comparison, only the key. Keys are used to order the
+# tree, so when we will constantly be reading records off the disk solely to get
+# their key value.
+#
+# If a record is read for its key, but the record is not soon visited by a
+# cursor, it will eventually be collected by a cache purge, while the key will
+# be retained, if the key is frequently consulted by queries as they descend the
+# tree. If the key is subset of a large record, purging the records and
+# retaining the keys will reduce the in‑memory size of the b‑tree.
+#
+# Also, the comparator is pretty easily generalized, while the exractor is
+# invariably specialized. You might have a single string comparator that you use
+# with extractors specialized for different types of records.
 #
 # ### Default Collation
 #
 # You will almost certainly define your down extractors and comparators, but the
-# b‑tree has a default that works for b‑tree that stores only JavaScript primitives.
+# b‑tree has a default that works for b‑tree that stores only
+# JavaScript primitives.

 # Default comparator is good only for strings, use a - b for numbers.
 comparator = (a, b) ->


maybe that helps

maybe that helps

--- app/assets/javascripts/effective_datatables/initialize.js.coffee
+++ app/assets/javascripts/effective_datatables/initialize.js.coffee
@@ -6,7 +6,7 @@
       init_options =
         bServerSide: true
         bProcessing: true
-        bSaveState: true
+        stateSave: true
         bAutoWidth: false
         deferRender: true
         order: datatable.data('default-order')


Edited "Gorongosa as a Case Study"

Edited "Gorongosa as a Case Study"

Added a comma after "standards" and hyphenated "hands-on"

--- app/pages/about/education.cjsx
+++ app/pages/about/education.cjsx
@@ -12,7 +12,7 @@

Gorongosa National Park is one of the most diverse ecosystems in the world and is undergoing a large-scale restoration effort following decades of war in Mozambique. Park managers and scientists are working together to better understand the ecology of the park and to protect the Gorongosa ecosystem and its wildlife. This ongoing effort is an ideal case study to teach concepts in ecology and conservation biology in the classroom.

-
HHMI BioInteractive has developed free educational resources, which are aligned to national curriculum standards that educators can use to teach concepts in ecology, conservation, social science, and inquiry-based science practices. Visit the BioInteractive website to explore a variety of Gorongosa resources, including films, interactives, and hands on activities.

+
HHMI BioInteractive has developed free educational resources, which are aligned to national curriculum standards, that educators can use to teach concepts in ecology, conservation, social science, and inquiry-based science practices. Visit the BioInteractive website to explore a variety of Gorongosa resources, including films, interactives, and hands-on activities.

Explore Resources


cleaning up how the giphy plugin instance is instrumented

--- test/giphy.spec.coffee
+++ test/giphy.spec.coffee
@@ -69,6 +69,8 @@
       should.exist @giphy

   describe 'hubot script', ->
+    giphyPluginInstance = null
+
     # helper function to confirm hubot responds to the correct input
     testHubot = (spy, input, args) ->
       [callback, other, ...] = spy
@@ -82,30 +84,32 @@
         callback.call null, args

     beforeEach ->
-      @fakes.stub @giphy, 'respond'
+      giphyPluginInstance = hubotGiphy @robot
+      @fakes.stub giphyPluginInstance.api, '_request', (options, callback) -> callback 'XHR Attempted', null
+      @fakes.stub giphyPluginInstance, 'respond'

     it 'has an active respond trigger', ->
       @robot.respond.should.have.been.called.once

     it 'responds to giphy command without args', ->
       testHubot @robot.respond, 'giphy', 'testing'
-      @giphy.respond.should.have.been.calledWith 'testing'
+      giphyPluginInstance.respond.should.have.been.calledWith 'testing'

     it 'responds to giphy command with args', ->
       testHubot @robot.respond, 'giphy test', 'testing'
-      @giphy.respond.should.have.been.calledWith 'testing'
+      giphyPluginInstance.respond.should.have.been.calledWith 'testing'

     it 'does not respond to non-giphy command without args', ->
       testHubot @robot.respond, 'notgiphy'
-      @giphy.respond.should.not.have.been.called
+      giphyPluginInstance.respond.should.not.have.been.called

     it 'does not respond to non-giphy command with args', ->
       testHubot @robot.respond, 'notgiphy test'
-      @giphy.respond.should.not.have.been.called
+      giphyPluginInstance.respond.should.not.have.been.called

     it 'does not respond to giphy command with a leading space', ->
       testHubot @robot.respond, ' giphy test'
-      @giphy.respond.should.not.have.been.called
+      giphyPluginInstance.respond.should.not.have.been.called

     it 'matches giphy command args', ->
       responder = @robot.respond.getCalls()[0]


fix default setting of sort order

fix default setting of sort order

--- app/assets/javascripts/hanuman/components/new_question.js.coffee
+++ app/assets/javascripts/hanuman/components/new_question.js.coffee
@@ -6,7 +6,7 @@

   setNewModel: ->
     model = @get('surveyStep').get('questions').content.createRecord(
-      sortOrder: @get('surveyStep').get('questions.length')
+      sortOrder: @get('surveyStep').get('questions.length') + 1
       questionText: ''
     )
     @set('model', model)


remove extra parameter for #newPage()

remove extra parameter for #newPage()

--- app/pages/lab/subject-set.cjsx
+++ app/pages/lab/subject-set.cjsx
@@ -109,7 +109,7 @@
       <div>
         <SubjectSetListingTable subjects={@state.subjects} onPreview={@previewSubject} onRemove={@removeSubject} />
         <nav className="pagination">
-          Page <select value={@props.page} disabled={@state.pageCount < 2 or isNaN @state.pageCount} onChange={(e) => @newPage(e.target.value, @state.pageCount)}>
+          Page <select value={@props.page} disabled={@state.pageCount < 2 or isNaN @state.pageCount} onChange={(e) => @newPage(e.target.value)}>
             {if isNaN @state.pageCount
               <option>?</option>
             else


update subject list when removing a subject

--- app/pages/lab/subject-set.cjsx
+++ app/pages/lab/subject-set.cjsx
@@ -127,6 +127,7 @@

   removeSubject: (subject) ->
     @props.subjectSet.removeLink('subjects', subject.id).then =>
+      @setSubjectResources(@props.subjectSet.id, @state.page)
       announceSetChange()

 EditSubjectSetPage = React.createClass


Use ResourceInput for subject set name

Use ResourceInput for subject set name

--- app/pages/lab/subject-set.cjsx
+++ app/pages/lab/subject-set.cjsx
@@ -1,4 +1,5 @@
 React = require 'react'
+ResourceInput = require '../../components/resource-input'
 handleInputChange = require '../../lib/handle-input-change'
 PromiseRenderer = require '../../components/promise-renderer'
 apiClient = require '../../api/client'
@@ -107,9 +108,7 @@
 EditSubjectSetPage = React.createClass
   displayName: 'EditSubjectSetPage'

-  mixins: [BoundResourceMixin, Navigation]
-
-  boundResource: 'subjectSet'
+  mixins: [Navigation]

   getDefaultProps: ->
     subjectSet: null
@@ -130,11 +129,12 @@

       <form onSubmit={@handleSubmit}>

-          Name

-          <input type="text" name="display_name" value={@props.subjectSet.display_name} className="standard-input full" onChange={@handleChange} />
+          <ResourceInput resource={@props.subjectSet} update="display_name" className="standard-input full">
+            Name
+

+          </ResourceInput>
           A subject set’s name is only seen by the science team.

-
<button type="submit" className="standard-button" disabled={not @props.subjectSet.hasUnsavedChanges()}>Save changes</button> {@renderSaveStatus()}

       </form>


testing bug fix

testing bug fix

--- test/index.spec.coffee
+++ test/index.spec.coffee
@@ -26,5 +26,5 @@

       it 'should respond with a text body', (done) ->
         request @uri + '/register', (error, response, body) ->
-          expect(body).length.gt(1000)
+          expect(body).length.gt(100)
           done()


:art: refactoring

:art: refactoring

--- lib/text-object.coffee
+++ lib/text-object.coffee
@@ -72,34 +72,36 @@
         ['scanInBufferRange', @rangeToEndOfFile(fromPoint)]

     nest = 0
-    point = null
+    found = null # We will search to fill this var.
+
     @editor[scanFunc] pattern, scanRange, ({matchText, range, stop}) =>
       charPre = @editor.getTextInBufferRange(range.traverse([0, -1], [0, -1]))
       return if charPre is '\\' # Skip escaped char with '\'

       # don't search across line unless specific pair.
       if @needStopSearch(pair, cursorPoint.row, range.start.row)
-        stop(); return
+        return stop()

       if search is searchPair
         if backward
           text = @editor.lineTextForBufferRow(fromPoint.row)
           if @isStartingPair(text[0..range.end.column], search)
-            point = range.end
+            found = range
         else
+          # skip for pair not within cursorPoint.
           if range.end.isLessThan(cursorPoint)
-            stop(); return
+            stop()
           else
-            point = range.end
+            found = range
       else
         lastChar = matchText[matchText.length-1]
         switch lastChar
           when search
-            if (nest is 0) then point = range.end else nest--
+            if (nest is 0) then found = range else nest--
           when searchPair
             nest++
-      stop() if point
-    point
+      stop() if found
+    if found? then found.end else null

   getRange: (selection, pair) ->
     if originallyEmpty = selection.isEmpty()


Add tests.

Add tests.

--- test/popup.spec.coffee
+++ test/popup.spec.coffee
@@ -13,12 +13,21 @@
         setFixtures window.__html__['test/fixtures/form.html']
         window.tabahead window.jQuery, window.fuzzy, window.chrome, window.setTimeout

+    describe 'loaded without exploding', ->
+        it 'is available', ->
+            (expect window.tabahead).toBeDefined()
+            (expect window.tabahead).toEqual jasmine.any Function
+
     describe 'expects the chrome environment', ->
         it 'expects to find a form.navbar-search', ->
             (expect $ 'body').toContain 'form.navbar-search'

         it 'expects to find an input#typeahead', ->
             (expect $ 'form.navbar-search').toContain 'input#typeahead'
+
+    describe 'Initially the input', ->
+        it 'should be focused', ->
+            (expect $ 'input#typeahead').toBeFocused()

     describe 'Typing some text into the input field', ->
         getCurrentWindowSpy = {}


Remove logs

Remove logs

--- client/app/modules/auxiliaries.coffee
+++ client/app/modules/auxiliaries.coffee
@@ -102,9 +102,6 @@

     A = compact(A)
     B = compact(B)
-
-    console.log(A, B)
-    console.log(A.length, B.length)

     while a < A.length
         b2 = b


-added some more THREE.js wrappers to maths module (temp)

--- src/maths/maths.coffee
+++ src/maths/maths.coffee
@@ -18,7 +18,28 @@
   constructor:( options )->
     super(options)

+#lines, curves
+class Spline extends THREE.Spline
+  constructor:( options )->
+    super(options)
+
+class SplineCurve3 extends THREE.SplineCurve3
+
+class ArcCurve extends THREE.ArcCurve
+
+#TODO: unify bezier curve types under common api
+class CubicBezierCurve3 extends THREE.CubicBezierCurve3
+
+class QuadraticBezierCurve3 extends THREE.QuadraticBezierCurve3
+
+#other
+class Euler extends THREE.Euler
+  constructor:( options )->
+    super(options)
+
 module.exports =
   Vector2: Vector2
   Vector3: Vector3
+  Spline3: SplineCurve3
+  Euler : Euleur


Fix references in test

Fix references in test

--- test/streetfood.coffee
+++ test/streetfood.coffee
@@ -4,7 +4,7 @@

 expect = chai.expect

-describe 'leaderboard', ->
+describe 'streetfood', ->
   beforeEach ->
     @robot =
       respond: sinon.spy()
@@ -13,7 +13,7 @@
       	data: {}
       }

-    require('../src/leaderboard')(@robot)
+    require('../src/streetfood')(@robot)

   it 'registers a respond listener', ->
     expect(@robot.respond).to.have.been.calledWith(//)


repl: allowing tab completion of an empty line and masking `__X` reserved helpers from tab completion; fixes #1583

--- repl.coffee
+++ repl.coffee
@@ -94,9 +94,11 @@

 # Attempt to autocomplete an in-scope free variable: `one`.
 completeVariable = (text) ->
-  if free = (text.match SIMPLEVAR)?[1]
+  free = (text.match SIMPLEVAR)?[1]
+  if free?
     vars = Script.runInContext 'Object.getOwnPropertyNames(this)', sandbox
-    possibilities = vars.concat CoffeeScript.RESERVED
+    keywords = (r for r in CoffeeScript.RESERVED when r[0..1] isnt '__')
+    possibilities = vars.concat keywords
     completions = getCompletions free, possibilities
     [completions, free]


Only re-fetch stats if the collection is empty

Since this lives on the field model, these stats will remain after
the first fetch.

--- coffee/cilantro/ui/field/stats.coffee
+++ coffee/cilantro/ui/field/stats.coffee
@@ -51,7 +51,8 @@
             if @model.stats?
                 @values.show new FieldStatsValues
                     collection: @model.stats
-                @model.stats.fetch(reset: true)
+                if not @model.stats.length
+                    @model.stats.fetch(reset: true)

     { FieldStats }


don't use baseEditState

don't use baseEditState

--- coffee/services/issueEditState.coffee
+++ coffee/services/issueEditState.coffee
@@ -106,7 +106,7 @@
      show error message.
     ###
     loadError: (data, status) =>
-      if status is BaseEditState.STATUS_CANCEL then return
+      if status is IssueEditState.STATUS_CANCEL then return
       Message.toast Resource.string("msgLoadIssueFail")


change to 2-space-indent

change to 2-space-indent

--- test/async-integration.coffee
+++ test/async-integration.coffee
@@ -3,66 +3,66 @@
 hinoki = require '../src/hinoki'

 factories =
-    count: (xs) ->
-        Promise.delay(xs.length, 100)
-    mean: (xs, count) ->
-        reducer = (acc, x) ->
-            acc + x
-        Promise.delay(xs.reduce(reducer, 0) / count, 100)
-    meanOfSquares: (xs, count) ->
-        reducer = (acc, x) ->
-            acc + x * x
-        Promise.delay(xs.reduce(reducer, 0) / count, 100)
-    variance: (mean, meanOfSquares) ->
-        Promise.delay(meanOfSquares - mean * mean, 100)
+  count: (xs) ->
+    Promise.delay(xs.length, 100)
+  mean: (xs, count) ->
+    reducer = (acc, x) ->
+      acc + x
+    Promise.delay(xs.reduce(reducer, 0) / count, 100)
+  meanOfSquares: (xs, count) ->
+    reducer = (acc, x) ->
+      acc + x * x
+    Promise.delay(xs.reduce(reducer, 0) / count, 100)
+  variance: (mean, meanOfSquares) ->
+    Promise.delay(meanOfSquares - mean * mean, 100)

 module.exports =

-    'ask for count': (test) ->
-        c = hinoki.newContainer factories,
-            xs: [1, 2, 3, 6]
+  'ask for count': (test) ->
+    c = hinoki.newContainer factories,
+      xs: [1, 2, 3, 6]

-        hinoki.inject c, (count) ->
-            test.equals count, 4
-            test.deepEqual c.instances,
-                xs: [1, 2, 3, 6]
-                count: 4
-            test.done()
+    hinoki.inject c, (count) ->
+      test.equals count, 4
+      test.deepEqual c.instances,
+        xs: [1, 2, 3, 6]
+        count: 4
+      test.done()

-    'ask for mean': (test) ->
-        c = hinoki.newContainer factories,
-            xs: [1, 2, 3, 6]
+  'ask for mean': (test) ->
+    c = hinoki.newContainer factories,
+      xs: [1, 2, 3, 6]

-        hinoki.inject c, (mean) ->
-            test.equals mean, 3
-            test.deepEqual c.instances,
-                xs: [1, 2, 3, 6]
-                count: 4
-                mean: 3
-            test.done()
+    hinoki.inject c, (mean) ->
+      test.equals mean, 3
+      test.deepEqual c.instances,
+        xs: [1, 2, 3, 6]
+        count: 4
+        mean: 3
+      test.done()

-    'ask for meanOfSquares': (test) ->
-        c = hinoki.newContainer factories,
-            xs: [1, 2, 3, 6]
+  'ask for meanOfSquares': (test) ->
+    c = hinoki.newContainer factories,
+      xs: [1, 2, 3, 6]

-        hinoki.inject c, (meanOfSquares) ->
-            test.equals meanOfSquares, 12.5
-            test.deepEqual c.instances,
-                xs: [1, 2, 3, 6]
-                count: 4
-                meanOfSquares: 12.5
-            test.done()
+    hinoki.inject c, (meanOfSquares) ->
+      test.equals meanOfSquares, 12.5
+      test.deepEqual c.instances,
+        xs: [1, 2, 3, 6]
+        count: 4
+        meanOfSquares: 12.5
+      test.done()

-    'ask for variance': (test) ->
-        c = hinoki.newContainer factories,
-            xs: [1, 2, 3, 6]
+  'ask for variance': (test) ->
+    c = hinoki.newContainer factories,
+      xs: [1, 2, 3, 6]

-        hinoki.inject c, (variance) ->
-            test.equals variance, 3.5
-            test.deepEqual c.instances,
-                xs: [1, 2, 3, 6]
-                count: 4
-                mean: 3
-                meanOfSquares: 12.5
-                variance: 3.5
-            test.done()
+    hinoki.inject c, (variance) ->
+      test.equals variance, 3.5
+      test.deepEqual c.instances,
+        xs: [1, 2, 3, 6]
+        count: 4
+        mean: 3
+        meanOfSquares: 12.5
+        variance: 3.5
+      test.done()


Fixed typo.

Fixed typo.

--- app/assets/javascripts/app/controllers/ticket_zoom/article_new.coffee
+++ app/assets/javascripts/app/controllers/ticket_zoom/article_new.coffee
@@ -158,7 +158,7 @@
     if @subscribeIdTextModule
       App.Ticket.unsubscribe(@subscribeIdTextModule)

-    @(window).off 'click.ticket-zoom-select-type'
+    $(window).off 'click.ticket-zoom-select-type'
     $(window).on 'click.ticket-zoom-textarea'

   render: ->