Fixed and cleaned up LongInt division code.

--- lib/long_int.coffee
+++ lib/long_int.coffee
@@ -101,29 +101,23 @@
     r

 divmod = (r, s) ->
-  log "divmod(#{dump(r)}, #{dump(s)})"
   step = (q, h, t, shift) ->
-    log "step(#{dump(q)}, #{dump(h)}, #{dump(t)}, #{shift})"
-    n = h.last() * if shift then BASE else 1
+    n = (h?.last() * if shift then BASE else 1) or 0
     d = s.last() + 1
-    f = Math.floor n / d
-    log "  n = #{n}, d = #{d}, f = #{f}"
-    [q_, h_] = if f
-      [add(q, new Stream(f)), cleanup(sub(h, streamTimesDigit(s, f)))]
-    else
-      [q, h]
-    log "  [q_, h_] = [#{dump(q_)},#{dump(h_)}]"
-    if cmp(h_, s) >= 0
-      log "  f += 1"
-      [q_, h_] = [add(q_, ONE), cleanup(sub(h_, s))]
-      log "  [q_, h_] = [#{dump(q_)},#{dump(h_)}]"
-
-    if shift
+    f = (Math.floor n / d) or (if cmp(h, s) >= 0 then 1 else 0)
+    log "divmod step(#{dump(q)}, #{dump(h)}, #{dump(t)}, #{shift}) -- f = #{f}"
+    [q_, h_] = [add(q, new Stream(f)), cleanup(sub(h, streamTimesDigit(s, f)))]
+
+    if f and not shift
+      recur -> step(q_, h_, t, shift)
+    else if shift
       recur -> step(q_, h_, t, false)
     else if t
-      x = h_.get(if shift then s.size() else s.size()-1)
-      recur ->
-        step(new Stream(0, -> q_), new Stream(t.first(), -> h_), t.rest(), !!x)
+      recur -> step(
+        new Stream(0, -> q_),
+        new Stream(t.first(), -> h_),
+        t.rest(),
+        h_.last() != 0)
     else
       log "  returning [#{dump(q_)},#{dump(h_)}]"
       [q_, h_]
@@ -225,8 +219,17 @@

 if quicktest
   one = new LongInt 1
-  a = new LongInt -9950
+  a = new LongInt 9950
   a2 = a.times a
   a3 = a2.times a
   a3inc = a3.plus(one)
+  a3dec = a3.minus(one)
   log "(#{a}**3 + 1) / #{a}**2 = #{a3inc.div(a2)} (#{a3inc.mod(a2)})"
+  log ""
+  log "(#{a}**3 - 1) / #{a}**2 = #{a3dec.div(a2)} (#{a3dec.mod(a2)})"
+  log ""
+
+  b = new LongInt 111111111
+  c = new LongInt 37
+  log "#{b} / #{c} = #{b.div c} (#{b.mod c})"
+  log ""


Real failing test this time :) for issue https://github.com/the-grid/Flowerflip/issues/35

--- spec/Thenable.coffee
+++ spec/Thenable.coffee
@@ -1233,15 +1233,15 @@
       it 'should resolve', (done) ->

         failedComponent = (n,d) ->
-          n.tree 'failedComponent'
+          n.continue 'failedComponent'
           .deliver()
           .then (n) ->
             n.abort('failedComponent aborted')

         component = (n,d) ->
-          n.tree 'component'
+          n.continue 'component'
           .deliver d
-          .maybe failedComponent
+          .then failedComponent
           .else (c,d) ->
             d
           .then (c, d) ->
@@ -1257,16 +1257,16 @@
             c.branch 'tripled', (b, data) ->
               data * 3

-          .maybe failedComponent
-          .else (c,d) ->
-            d
+          # .maybe failedComponent
+          # .else (c,d) ->
+          #   d

           .then 'variation', (c, d) ->
             c.branch 'top', (b, data) ->
               data * 3
             c.branch 'bottom', (b, data) ->
               data * 3
-
+
            .all [component]
            .then (n,results) ->
              results[0]
@@ -1589,3 +1589,137 @@

       it 'should work', (done) ->
         test done
+
+
+  describe 'alt layout system simulation with branching variation', ->
+
+    test = (done) ->
+
+      failedComponent = (n,d) ->
+        n.tree 'failedComponent'
+        .deliver()
+        .then (n) ->
+          n.abort('failedComponent aborted')
+
+      text = (n,item) ->
+        n.tree 'text-component'
+        .deliver(item)
+        .then (n,item) ->
+          block = n.getBlock item, (b) ->
+            b.type is 'text'
+          n.expect( block ).to.be.an 'object'
+          n.eatBlock block
+          block
+        .else (n,d) ->
+          n.abort "text not there", d
+
+      video = (n,item) ->
+        n.tree 'video-component'
+        .deliver(item)
+        .then (n,item) ->
+          block = n.getBlock item, (b) ->
+            b.type is 'video'
+          n.expect( block ).to.be.an 'object'
+          n.eatBlock block
+          block
+        .else (n,d) ->
+          n.abort "video not there", d
+
+      image = (n,item) ->
+        n.tree 'image-component'
+        .deliver(item)
+        .then (n,item) ->
+          block = n.getBlock item, (b) ->
+            b.type is 'image'
+          n.expect( block ).to.be.an 'object'
+          n.eatBlock block
+          block
+        .else (n,d) ->
+          n.abort "image not there", d
+
+      post = (components) ->
+        return (n,d) ->
+          n.tree 'post'
+          .deliver()
+          .then (n) ->
+            item = n.getItem (item) ->
+              item?
+            n.eatItem item
+            item
+          .all components
+          .else (n,d) ->
+            n.abort('post: missing required component',d)
+
+      section = (name,posts) ->
+        return (n,d) ->
+          n.tree 'section'
+          .deliver()
+          .then name, ->
+            true
+          .then (c, d) ->
+            c.branch 'left', ->
+              d
+            c.branch 'right', ->
+              d
+          .then (c, d) ->
+            c.branch 'top', ->
+              d
+            c.branch 'bottom', ->
+              d
+          .all posts
+          .then (n,results) ->
+            name
+          .else (n,d) ->
+            n.abort('section: posts failed',d)
+
+      imageSection = section('imageSection',[
+          post([image])
+        ])
+
+      textSection = section('textSection',[
+          post([text])
+        ])
+
+      videoTextSection = section('videoTextSection',[
+          post([video,text])
+        ])
+
+      sections = [videoTextSection, imageSection, textSection]
+
+      layout = (n) ->
+        n.tree 'layout'
+        .deliver()
+        .contest sections
+          , (n, results) -> # scoring
+            return results[0]
+          , (n, chosen) -> # until
+            return false if n.availableItems().length
+            true
+
+      Root()
+      .deliver
+        items: [
+            id: 1
+            content: [
+              {type:'text'}
+              {type:'video'}
+            ]
+          ,
+            id: 2
+            content: [
+              {type:'text'}
+            ]
+          ,
+            id: 3
+            content: [
+              {type:'image'}
+            ]
+        ]
+      .then layout
+      .finally (n, results) ->
+        chai.expect(results.length).to.equal 3
+        chai.expect(results).to.eql ['videoTextSection','textSection','imageSection']
+        done()
+
+    it 'should work', (done) ->
+      test done


Run Sumatra with -reuse-instance

Run Sumatra with -reuse-instance

--- lib/viewer.coffee
+++ lib/viewer.coffee
@@ -9,7 +9,7 @@

   _jumpWindows: (texfile, pdffile, row, col, forward_sync, keep_focus) ->
     sumatra_cmd = atom.config.get("latextools.win32.sumatra")
-    sumatra_args = [] # ["-reuse-instance"]
+    sumatra_args = ["-reuse-instance"]

     if forward_sync
       sumatra_args = sumatra_args.concat(["-forward-search", '\"'+texfile+'\"', "#{row}"])


Update documentation.

Update documentation.

--- spec/compiler.coffee
+++ spec/compiler.coffee
@@ -32,7 +32,7 @@
 # @param message [String] This should be provided when a rule exists to catch
 # invalid syntax, and omitted when an error is expected to be thrown by the PEG
 # parser.
-# @param [Boolean] Whether the spec should be treated as pending.
+# @param pending [Boolean] Whether the spec should be treated as pending.
 #
 expectError = (source, message, pending) ->
   itFn = if pending then xit else it


Documentation improvement

Documentation improvement

--- lib/messages.coffee
+++ lib/messages.coffee
@@ -78,8 +78,8 @@
     ## Super-user can see all messages, even unpublished/deleted messages.
     group: group
   else if groupRoleCheck group, 'read', user
-    ## Regular users can see all messages they authored, plus
-    ## published undeleted messages by others.
+    ## Regular users can see all messages they authored, or are @mentioned in,
+    ## plus published undeleted messages by others.
     if user?.username
       group: group
       $or: [


Add sections

Add sections

--- src/directory.coffee
+++ src/directory.coffee
@@ -16,6 +16,10 @@

   realPath: null
   subscriptionCount: 0
+
+  ###
+  Section: Construction
+  ###

   # Public: Configures a new Directory instance, no files are accessed.
   #
@@ -37,6 +41,10 @@

     @lowerCasePath = @path.toLowerCase() if fs.isCaseInsensitive()

+  ###
+  Section: Event Subscription
+  ###
+
   # Public: Invoke the given callback when the directory's contents change.
   #
   # * `callback` {Function} to be called when the directory's contents change.
@@ -64,6 +72,10 @@
       Grim.deprecate("Use Directory::onDidChange instead")

     EmitterMixin::on.apply(this, arguments)
+
+  ###
+  Section: Directory Metadata
+  ###

   # Public: Returns the {String} basename of the directory.
   getBaseName: ->


Don't modify scanRanges in pattern provider function

--- lib/operator.coffee
+++ lib/operator.coffee
@@ -104,7 +104,13 @@
         if @isMode('visual')
           scanRanges = @editor.getSelectedBufferRanges()
           @vimState.modeManager.deactivate() # clear selection
-        @patternForOccurence ?= @getPatternForOccurrence(scanRanges)
+
+        unless @patternForOccurence
+          {pattern, bufferRange} = @getPatternAndBufferRangeForOccurrence()
+          @patternForOccurence = pattern
+          if scanRanges?.length
+            lastRangeIndex = scanRanges.length - 1
+            scanRanges[lastRangeIndex] = scanRanges[lastRangeIndex].union(bufferRange)

       @onDidSelectTarget =>
         scanRanges ?= @editor.getSelectedBufferRanges()
@@ -202,22 +208,22 @@
     @mutateSelections (selection) => @mutateSelection(selection)
     @activateMode(@finalMode, @finalSubmode)

-  getPatternForOccurrence: (scanRanges) ->
+  # Return {pattern, bufferRange},
+  #   - Mandatory: pattern
+  #   - Optional: bufferRange
+  getPatternAndBufferRangeForOccurrence: (scanRanges) ->
     if @hasRegisterName()
-      ///#{_.escapeRegExp(@getRegisterValueAsText())}///g
+      {pattern: ///#{_.escapeRegExp(@getRegisterValueAsText())}///g }
     else
       cursor = @editor.getLastCursor()
       char = getCharacterAtCursor(cursor)
       scope = cursor.getScopeDescriptor().getScopesArray()
       if isNonWordCharacter(char, scope)
-        ///#{_.escapeRegExp(char)}///g
+        {pattern: ///#{_.escapeRegExp(char)}///g }
       else
-        cursorWordRange = getCurrentWordBufferRange(cursor)
-        if scanRanges?.length
-          lastRangeIndex = scanRanges.length - 1
-          scanRanges[lastRangeIndex] = scanRanges[lastRangeIndex].union(cursorWordRange)
-        cursorWord = @editor.getTextInBufferRange(cursorWordRange)
-        ///\b#{_.escapeRegExp(cursorWord)}\b///g
+        bufferRange = getCurrentWordBufferRange(cursor)
+        cursorWord = @editor.getTextInBufferRange(bufferRange)
+        {pattern: ///\b#{_.escapeRegExp(cursorWord)}\b///g, bufferRange}

 # -------------------------
 class Select extends Operator


Fix difflib/diffview in editor

Fix difflib/diffview in editor

--- app/views/editor/DeltaView.coffee
+++ app/views/editor/DeltaView.coffee
@@ -3,8 +3,9 @@
 deltasLib = require 'core/deltas'
 modelDeltas = require 'lib/modelDeltas'
 jsondiffpatch = require('lib/jsondiffpatch')
-require 'vendor/scripts/diffview'
-require 'vendor/scripts/difflib'
+diffview = require 'exports-loader?diffview!vendor/scripts/diffview'
+require 'vendor/styles/diffview.css'
+difflib = require 'exports-loader?difflib!vendor/scripts/difflib'
 require 'lib/setupTreema'

 TEXTDIFF_OPTIONS =


Fixed typo.

Fixed typo.

--- lib/packages.coffee
+++ lib/packages.coffee
@@ -8,7 +8,7 @@
 		for repository in manifest.apt.repositories
 			target.spawn 'sudo', ['add-apt-repository', repository]

-	target.exec 'sudo apt-get update' if manifest.apt.udpate?
+	target.exec 'sudo apt-get update' if manifest.apt.update?

 # ---


missing negation

missing negation

--- src/dt-jquery.coffee
+++ src/dt-jquery.coffee
@@ -194,12 +194,12 @@
         if newtag._jquery_insert is true
             that = this
             newtag._jquery_replace ?= oldtag._jquery_replace
-            noreplacerequest = newtag._jquery_replace?
+            oldreplacerequest = newtag._jquery_replace?
             newtag._jquery_replace ?= singlton_callback newtag, ->
                 that.fn.replace(oldtag, this)
             newtag._jquery_replace.replace?(newtag)
             oldtag._jquery_replace = null
-            if noreplacerequest
+            unless oldreplacerequest
                 @animation.push(newtag._jquery_replace)

     ontext: (el, text) ->


Add link in data table to simulation results

--- js/data_table.coffee
+++ js/data_table.coffee
@@ -16,6 +16,8 @@
   else
     data.name

+fbenchmark = (data, type, row) ->
+  '' + data + ''

 create_table = (data_in) ->
   data_raw = data_in['data']
@@ -42,6 +44,7 @@
       {
         data: "id_"
         title: "Benchmark"
+        render: fbenchmark
       }
       {
         data: "author"


minor change

minor change

--- lib/settings.coffee
+++ lib/settings.coffee
@@ -36,4 +36,5 @@
   closeItemsForRemovedProject:
     order: 5
     type: 'boolean'
-    default: true
+    default: false
+    description: 'close editor when containing project was removed'


Support for different environments

Support for different environments

--- src/fitCurves.coffee
+++ src/fitCurves.coffee
@@ -170,4 +170,10 @@

 normalize = (v) -> math.divide(v, math.norm(v))

-module.exports = fitCurve
+if exports?
+    if module? and module.exports?
+        module.exports = fitCurve
+    else
+        exports.fitCurve = fitCurve
+else if window
+    window.fitCurve = fitCurve


use python v3.4.3

use python v3.4.3

--- lib/wakatime.coffee
+++ lib/wakatime.coffee
@@ -151,9 +151,9 @@

 installPython = () ->
   if os.type() is 'Windows_NT'
-    url = 'https://www.python.org/ftp/python/3.4.2/python-3.4.2.msi';
+    url = 'https://www.python.org/ftp/python/3.4.3/python-3.4.3.msi';
     if os.arch().indexOf('x64') > -1
-      url = "https://www.python.org/ftp/python/3.4.2/python-3.4.2.amd64.msi";
+      url = "https://www.python.org/ftp/python/3.4.3/python-3.4.3.amd64.msi";
     console.log 'Downloading python...'
     msiFile = __dirname + path.sep + 'python.msi'
     downloadFile(url, msiFile, ->


iframe support

iframe support

--- src/graphview.coffee
+++ src/graphview.coffee
@@ -3,19 +3,35 @@
   setUp: =>
     @updateWindowSize()
     $(window).resize(@updateWindowSize)
-    @currentImage = 0
-    @preloadImage(@getURL(@config.graphs[0]))
-    @displayImage()
+    @currentGraph = 0
+    @preloadGraph(@config.graphs[0])
+    @displayGraph()
+
+  preloadGraph: (graph) ->
+    if graph.source == 'iframe'
+      @preloadIframe(graph.url)
+    else
+      @preloadImage(@getURL(graph))

   preloadImage: (url) ->
-    @img = new Image()
-    @img.src = url
+    @graph = new Image()
+    @graph.src = url

-  displayImage: =>
-    $('#container').html(@img)
-    @currentImage = (@currentImage + 1) % @config.graphs.length
-    setTimeout(@displayImage, @config.settings.dwellTime)
-    @preloadImage(@getURL(@config.graphs[@currentImage]))
+  preloadIframe: (url) ->
+    @graph = $(document.createElement('iframe'))
+    @graph.attr({
+      src: url,
+      width: @imageWidth,
+      height: @imageHeight,
+      scrolling: "no",
+      frameborder: "no"
+    })
+
+  displayGraph: =>
+    $('#container').html(@graph)
+    @currentGraph = (@currentGraph + 1) % @config.graphs.length
+    setTimeout(@displayGraph, @config.settings.dwellTime)
+    @preloadGraph(@config.graphs[@currentGraph])

   getURL: (graph) ->
     if graph.source == 'graphite'


document the dispatcher

document the dispatcher

--- src/dispatcher.coffee
+++ src/dispatcher.coffee
@@ -8,7 +8,7 @@
 	class Dispatcher

 		###
-		# @var {boolean} dispatching Wether or not the dispatcher is currently dispatching
+		# @var {boolean} dispatching Wether or not the dispatcher is currently dispatching.
 		# @private
 		###
 		dispatching = no
@@ -19,38 +19,47 @@
 		storeID = 0

 		###
-		# @var {object} stores
+		# @var {object} stores Store registry.
 		# @private
 		###
 		stores = {}
 		###
+        # @var {object} isPending Object for tracking pending store callbacks.
 		# @private
 		###
 		isPending = {}
 		###
+        # @var {object} isPending Object for tracking handled store callbacks.
 		# @private
 		###
 		isHandled = {}

 		###
+        # @var {string} isPending The current action being dispatched, if any.
 		# @private
 		###
 		currentAction = null
 		###
+        # @var {mixed} isPending The current payload being dispatched, if any.
 		# @private
 		###
 		currentPayload = null

 		###
+        # @var {object} Signal triggered when the dispatcher is started.
 		# @public
 		###
 		started: new Signal()
 		###
+        # @var {object} Signal triggered when the dispatcher is stopped.
 		# @public
 		###
 		stopped: new Signal()

 		###
+        # Sets the dispatcher to a state where all stores are neither
+        # pending nor handled.
+        #
 		# @private
 		###
 		prepareForDispatching = () ->
@@ -63,6 +72,8 @@
 			@started.dispatch()

 		###
+        # Resets the dispatcher state after dispatching.
+        #
 		# @private
 		###
 		finalizeDispatching = () ->
@@ -73,6 +84,10 @@
 			@stopped.dispatch()

 		###
+        # Calls the action handler on a store with the current action and payload.
+        # This method is used when dispatching.
+        #
+        # @param {integer} id The ID of the store to notify
 		# @private
 		###
 		notifyStore = (id) ->
@@ -83,15 +98,35 @@
 			stores[id]._handleAction.call stores[id], currentAction, currentPayload, @waitFor
 			isHandled[id] = yes

+        ###
+        # Registers a store with the dispatcher so that it's notified when actions
+        # are dispatched.
+        #
+        # @param {Object} store The store to register with the dispatcher
+        ###
 		register: (store) ->
 			stores[storeID] = store
 			store._id = storeID++;

+
+        ###
+        # Unregisters a store from the dispatcher so that it's no longer
+        # notified when actions are dispatched.
+        #
+        # @param {Object} store The store to unregister from the dispatcher
+        ###
 		unregister: (store) ->
 			invariant store._id? and stores[store._id]?,
 				"dispatcher.unregister(...): Store is not registered with the dispatcher."
 			delete stores[store._id]

+        ###
+        # Method for waiting for other stores to complete their handling
+        # of actions. This method is passed along to the Stores when an action
+        # is dispatched.
+        #
+        # @see notifyStore
+        ###
 		waitFor: (storeDependencies...) =>
 			# We can only wait for dependencies if the dispatcher is dispatching.
 			# In other words, waitFor() has to be called inside an action handler.
@@ -119,6 +154,13 @@
 				# Make the dependency handle the action.
 				notifyStore.call @, id

+        ###
+        # Method for dispatching in action. This method is used by the Action class
+        # when calling Action.dispatch().
+        #
+        # @param {string} actionName The name of the action to dispatch
+        # @param {mixed} payload The payload for the event.
+        ###
 		dispatch: (actionName, payload) =>
 			# The flux architecture dictates that an action cannot
 			# immediately trigger another action, which leads to cascading


Lintify

Lintify

--- src/installer.coffee
+++ src/installer.coffee
@@ -46,7 +46,7 @@
         process.stdout.write '\u2717\n'.red
         callback(stdout.red + stderr.red)

-  determineVsInstallArgs: () ->
+  determineVsInstallArgs: ->
     return null unless config.isWin32

     # NB: We want to prefer 2012 over 2010 if both are installed. This
@@ -55,7 +55,7 @@
     return "--msvs_version=2012" if config.isVs2012Installed()
     return "--msvs_version=2010" if config.isVs2010Installed()

-    throw "You must have either VS2010 or VS2012 installed"
+    throw new Error("You must have either VS2010 or VS2012 installed")

   installModule: (options, modulePath, callback) ->
     process.stdout.write "Installing #{modulePath} to #{@atomPackagesDirectory} "


Fix indented ordered list

Fix indented ordered list

--- src/js/esarea.coffee
+++ src/js/esarea.coffee
@@ -66,9 +66,10 @@
       return
     e.preventDefault()
     listMark = match[1]
-    if listMarkMatch = listMark.match /^(\d+)\./
-      num = parseInt(listMarkMatch[1])
-      listMark = listMark.replace(/^\d+/, num + 1) unless num == 1
+    if listMarkMatch = listMark.match /^(\s*)(\d+)\./
+      indent = listMarkMatch[1]
+      num    = parseInt(listMarkMatch[2])
+      listMark = listMark.replace(/\s*\d+/, "#{indent}#{num + 1}") unless num == 1
     $(e.target).selection('insert', {text: "\n" + listMark, mode: 'before'});
   else if currentLine.text.match(/^(\s*(?:-|\+|\*|\d+\.) )/)
     # remove list


use keymaster instead of ace for keybindings.

keybindings now work anywhere in the window, not just when ace has
focus!

--- src/keybinder.coffee
+++ src/keybinder.coffee
@@ -1,11 +1,7 @@
 ace = require 'ace/ace'
 canon = require 'pilot/canon'

+key = require 'keymaster'
+
 exports.bindKey = (name, shortcut, callback) ->
-  canon.addCommand
-    name: name
-    exec: callback
-    bindKey:
-      win: null
-      mac: shortcut
-      sender: 'editor'
+  key shortcut, -> callback(); false


bug fix

bug fix

--- fork_thread.coffee
+++ fork_thread.coffee
@@ -38,6 +38,7 @@
   @socket.send \
    {method: method, data: data}, \
    (data) ->
-    cb data.err, data.data
+    if cb?
+     cb data.err, data.data

 exports.ForkThread = ForkThread


Add request defaults

Add request defaults

--- src/nodeswork.coffee
+++ src/nodeswork.coffee
@@ -69,6 +69,9 @@
       url: "#{@opts.server}#{opts.url}"
     }

+  requestDefaults: (opts) ->
+    @requestClient = @requestClient.defaults opts
+
   start: (cb) ->
     @config @_opts

@@ -113,6 +116,7 @@
   }
   [ctx.user, ctx.configs]  = [user, configs]
   ctx.accounts             = parseAccount nw, accounts
+  ctx.nodeswork            = nw

   await next()


Pass options to ::publishPackage

Pass options to ::publishPackage

--- src/publisher.coffee
+++ src/publisher.coffee
@@ -48,4 +48,4 @@
         else
           @publishPackage(options)
     else
-      @publishPackage()
+      @publishPackage(options)


Remove comment.

Remove comment.

--- app/client/js/dispatcher.coffee
+++ app/client/js/dispatcher.coffee
@@ -87,12 +87,10 @@
    @return {!goog.Promise}
   ###
   waitFor: (indexes) ->
+    goog.asserts.assert @isDispatching_, 'Must be invoked while dispatching.'
     goog.asserts.assertArray indexes
-    goog.asserts.assert @isDispatching_, 'Must be invoked while dispatching.'
     for index in indexes
       goog.asserts.assertFunction @callbacks_[index],
         "`#{index}` does not map to a registered callback."
-
-    # `%s` does not map to a registered callback.'
     promises = indexes.map (index) => @promises_[index]
     goog.Promise.all promises


changed thumbnail and preview to be smaller

--- app/coffee/FileConverter.coffee
+++ app/coffee/FileConverter.coffee
@@ -1,4 +1,3 @@
-easyimage = require("easyimage")
 _ = require("underscore")
 metrics = require("./metrics")
 logger = require("logger-sharelatex")
@@ -29,7 +28,7 @@
 			dst: destPath
 			width: 424
 			height: 300
-		args = "nice convert -flatten -background white -resize 300x -density 300 #{sourcePath} #{destPath}"
+		args = "nice convert -flatten -background white -resize 260x -density 300 #{sourcePath} #{destPath}"
 		exec args, (err, stdout, stderr)->
 			callback(err, destPath)

@@ -42,6 +41,6 @@
 			dst: destPath
 			width: 600
 			height: 849
-		args = "nice convert -flatten -background white -resize 600x -density 300 #{sourcePath} #{destPath}"
+		args = "nice convert -flatten -background white -resize 548x -density 300 #{sourcePath} #{destPath}"
 		exec args, (err, stdout, stderr)->
 			callback(err, destPath)


fix balance deduct bug

fix balance deduct bug

--- app/js/controllers/wheel.coffee
+++ app/js/controllers/wheel.coffee
@@ -149,7 +149,7 @@
     GameAPI.play($scope.game_name, gameParam).then (resp) ->
       console.log resp

-      $scope.current_balance.amount -= gameParam.amount
+      $scope.current_balance.amount -= gameParam.amount * $scope.current_balance.precision

       refresh_balance()


Show component descriptions in the listing

--- src/bin/noflo.coffee
+++ src/bin/noflo.coffee
@@ -3,6 +3,7 @@
 noflo = require "noflo"
 cli = require "cli"
 path = require "path"
+{_} = require "underscore"

 cli.enable "help"
 cli.enable "version"
@@ -25,9 +26,14 @@
         baseDir = path.resolve process.cwd(), cli.args[1]
         loader = new noflo.ComponentLoader baseDir
         loader.listComponents (components) ->
-          for component, path of components
-            console.log "#{component} (#{path})"
-          process.exit 0
+          todo = 0
+          _.each components, (path, component) ->
+            instance = loader.load component, (instance) ->
+              todo--
+              console.log ''
+              console.log "#{component} (#{path})"
+              console.log instance.description if instance.description
+              process.exit 0 if todo is 0
         return

     for arg in cli.args


clampable

clampable

--- src/clampable.coffee
+++ src/clampable.coffee
@@ -1,5 +1,4 @@
 Clampable = (I={}, self) ->
-  # Set some default properties
   Object.reverseMerge I,
     clampData: {}


Patreon infinite loop fix.

Patreon infinite loop fix.

--- packages/artificialengines/telepathy/patreon-server.coffee
+++ packages/artificialengines/telepathy/patreon-server.coffee
@@ -78,7 +78,7 @@

         pledge

-  @_call: (url) ->
+  @_call: (url, dontRetry = false) ->
     AT.Patreon._client(url).then (result) =>
       # Return result.
       result
@@ -86,11 +86,13 @@
     .catch (error) =>
       switch error.error.status
         when 401, 403
+          return if dontRetry
+
           # We need to refresh the access token.
           @refreshClient()

-          # Repeat the call.
-          @_call url
+          # Repeat the call, but don't retry again if the call still fails to prevent infinite loops.
+          @_call url, true

         else
           console.error "Error accessing Patreon API.", error


Get a few benchmarks in there

Get a few benchmarks in there

--- benchmark/benchmark-suite.coffee
+++ benchmark/benchmark-suite.coffee
@@ -59,19 +59,34 @@
         editor.insertText('"')
         editor.backspace()

-    fdescribe "calculating-pixel-position.", ->
+    describe "positionLeftForLineAndColumn.", ->
       line = null
       beforeEach ->
         editor.scrollTop(2000)
         editor.resetDisplay()
         line = editor.lineElementForScreenRow(106)[0]

-      benchmark "positionLeftForLineAndColumn", 20000, ->
-        editor.positionLeftForLineAndColumn(line, 82)
-        editor.pixelLeftCache.delete(line)
+      describe "one-line.", ->
+        beforeEach ->
+          editor.clearCharacterWidthCache()

-      benchmark "positionLeftForLineAndColumn.cached", 20000, ->
-        editor.positionLeftForLineAndColumn(line, 82)
+        benchmark "uncached", 5000, ->
+          editor.positionLeftForLineAndColumn(line, 106, 82)
+          editor.clearCharacterWidthCache()
+
+        benchmark "cached", 5000, ->
+          editor.positionLeftForLineAndColumn(line, 106, 82)
+
+      describe "multiple-lines.", ->
+        [firstRow, lastRow] = []
+        beforeEach ->
+          firstRow = editor.getFirstVisibleScreenRow()
+          lastRow = editor.getLastVisibleScreenRow()
+
+        benchmark "cache-entire-visible-area", 100, ->
+          for i in [firstRow..lastRow]
+            line = editor.lineElementForScreenRow(i)[0]
+            editor.positionLeftForLineAndColumn(line, i, Math.max(0, editor.lineLengthForBufferRow(i)))

     describe "text-rendering.", ->
       beforeEach ->


correct varchar/nvarchar param length

correct varchar/nvarchar param length

--- src/data-type.coffee
+++ src/data-type.coffee
@@ -293,7 +293,7 @@
         length = @.maximumLength

       if length <= @maximumLength
-        "varchar(#{@.maximumLength})"
+        "varchar(#{length})"
       else
         "varchar(max)"
     writeParameterData: (buffer, parameter) ->
@@ -351,7 +351,7 @@
         length = @maximumLength

       if length <= @maximumLength
-        "nvarchar(#{@.maximumLength})"
+        "nvarchar(#{length})"
       else
         "nvarchar(max)"
     writeParameterData: (buffer, parameter) ->


background: just a comment

background: just a comment

--- cs/background.coffee
+++ cs/background.coffee
@@ -7,11 +7,13 @@
   console.lolg "\n#" + iteration

   if ls.useInfoscreen isnt 'true'
+    # Only if online, else keep good old
     if navigator.onLine
       updateHours() if iteration % UPDATE_HOURS_INTERVAL is 0 and ls.showCantina is 'true'
       updateCantinas() if iteration % UPDATE_CANTINAS_INTERVAL is 0 and ls.showCantina is 'true'
       updateAffiliationNews '1' if iteration % UPDATE_NEWS_INTERVAL is 0 and ls.showAffiliation1 is 'true'
       updateAffiliationNews '2' if iteration % UPDATE_NEWS_INTERVAL is 0 and ls.showAffiliation2 is 'true'
+    # Only if hardware
     if Affiliation.org[ls.affiliationKey1].hw
       updateOfficeAndMeetings() if iteration % UPDATE_OFFICE_INTERVAL is 0 and ls.showOffice is 'true'
       updateCoffeeSubscription() if iteration % UPDATE_COFFEE_INTERVAL is 0 and ls.coffeeSubscription is 'true'


Set path as tab title attribute

Set path as tab title attribute

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

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


Somehow we forgot if >_>

Somehow we forgot if >_>

--- grammars/geck.cson
+++ grammars/geck.cson
@@ -13,6 +13,7 @@
       Float|\
       Function|\
       Begin|\
+      If|\
       EndIf|\
       ElseIf|\
       End|\


Improve pattern-matching for manuscript references

--- grammars/roff.cson
+++ grammars/roff.cson
@@ -639,12 +639,27 @@
 			begin: "(?x) ^([.'])[ \t]*
 				(1C|2C|AB|AE|AI|AU|B1|B2|BT|BX|DA|DE|DS|EN|EQ|FE|FS|IP|KE|KF|KS|LG
 				|LP|MC|ND|NH|NL|P1|PE|PP|PS|PT|PX|QP|RP|SH|SM|TA|TC|TE|TL|TS|XA|XE
-				|XP|XS|\\[|\\])\\b"
+				|XP|XS)\\b"
 			end: "(?<!\\\\)(?=\n)"
 			beginCaptures:
 				1: {name: "punctuation.definition.macro.roff"}
 				2: {name: "entity.name.roff"}
 			patterns: [{include: "#params"}]
+		},{
+
+			# Reference
+			name: "meta.function.reference.ms.macro.roff"
+			begin: "^([.'])[ \t]*(\\[)\\s*(\\\\\".*)?$"
+			end:   "^([.'])[ \t]*(\\])(?=\\s|$|\\\\\")"
+			contentName: "entity.reference.name.ms.macro.roff"
+			patterns: [include: "$self"]
+			beginCaptures:
+				1: {name: "punctuation.definition.macro.roff"}
+				2: {name: "punctuation.section.function.begin.roff"}
+				3: {patterns: [include: "#escapes"]}
+			endCaptures:
+				1: {name: "punctuation.definition.macro.roff"}
+				2: {name: "punctuation.section.function.begin.roff"}
 		},{

 			# Deprecated macros


Ensure a dialog is not opened when webContents crash in headless mode

--- src/main-process/atom-window.coffee
+++ src/main-process/atom-window.coffee
@@ -150,7 +150,10 @@
       @browserWindow.destroy() if chosen is 0

     @browserWindow.webContents.on 'crashed', =>
-      @atomApplication.exit(100) if @headless
+      if @headless
+        console.log "Renderer process crashed, exiting"
+        @atomApplication.exit(100)
+        return

       @fileRecoveryService.didCrashWindow(this)
       chosen = dialog.showMessageBox @browserWindow,


Config ok when no auth provider and logged in with email/password

--- src/remote/config-normalizer.coffee
+++ src/remote/config-normalizer.coffee
@@ -19,6 +19,7 @@
         config[k].credentials = c
     normalize: ()->
       @config.settings = @sortServicesByType @config.services.settings, @config.services.types
+      @config.settings.auth ?= {}
       @applyUserCredentials @config.settings.auth, @config.data.credentials
       @config


fix message

fix message

--- src/services/gateblu-service.coffee
+++ src/services/gateblu-service.coffee
@@ -96,7 +96,8 @@
     @updateDevices devices

   sendToGateway: (message, callback=->) =>
-    @meshbluConnection.message [@uuid], message, callback
+    newMessage = _.extend devices: [@uuid], message
+    @meshbluConnection.message newMessage, callback

   subscribeToDevices: (devices) =>
     _.each devices, (device) =>


webapp/account: switch to project page after sign in

--- src/smc-webapp/redux_account.coffee
+++ src/smc-webapp/redux_account.coffee
@@ -33,6 +33,7 @@
                     when 'sign_in_failed'
                         @setState(sign_in_error : mesg.reason)
                     when 'signed_in'
+                        require('./top_navbar').top_navbar.switch_to_page('projects')
                         break
                     when 'error'
                         @setState(sign_in_error : mesg.reason)
@@ -66,6 +67,7 @@
                         @setState('sign_up_error': mesg.reason)
                     when "signed_in"
                         ga('send', 'event', 'account', 'create_account')    # custom google analytic event -- user created an account
+                        require('./top_navbar').top_navbar.switch_to_page('projects')
                     else
                         # should never ever happen
                         # alert_message(type:"error", message: "The server responded with invalid message to account creation request: #{JSON.stringify(mesg)}")


Add test for resolve reference for Product Type.

--- src/spec/product-import.spec.coffee
+++ src/spec/product-import.spec.coffee
@@ -1,7 +1,7 @@
 _ = require 'underscore'
 _.mixin require('underscore-mixins')
-{ProductImport} = require '../lib'
-Config = require('../config')
+{ProductImport} = require '../coffee'
+Config = require('../../config')
 Promise = require 'bluebird'
 fs = require 'fs'

@@ -33,6 +33,21 @@
     variants: []
   }
 ]
+
+sampleProductTypeResponse =
+  body:
+    results: [
+      {
+        "id": "product_type_internal_id",
+        "version": 1,
+        "name": "AGS",
+        "description": "Gütesiegel",
+        "classifier": "Complex",
+        "attributes": [ ],
+        "createdAt": "2015-04-15T15:11:07.175Z",
+        "lastModifiedAt": "2015-04-15T15:11:07.175Z"
+      }
+    ]

 describe 'ProductImport', ->

@@ -76,4 +91,23 @@
       newProduct = sampleProducts[0]
       expect(@import._isExistingEntry(existingProduct,sampleProductProjectionResponse)).toBeDefined()
       expect(@import._isExistingEntry(existingProduct,sampleProductProjectionResponse).masterVariant.sku).toEqual "e"
-      expect(@import._isExistingEntry(newProduct,sampleProductProjectionResponse)).toBeUndefined()+      expect(@import._isExistingEntry(newProduct,sampleProductProjectionResponse)).toBeUndefined()
+
+  describe '::_resolveReference', ->
+
+    it 'should resolve product type reference and cache the result', (done) ->
+      @import._resetCache()
+      spyOn(@import.client.productTypes, "fetch").and.callFake => Promise.resolve(sampleProductTypeResponse)
+      productTypeRef = { id: 'AGS'}
+      @import._resolveReference(@import.client.productTypes, 'productType', productTypeRef, "name=\"#{productTypeRef.id}\"")
+        .then (result) =>
+          console.log("Entered")
+          expect(result).toEqual sampleProductTypeResponse.body.results[0]
+          console.log("Cached result: ")
+          console.log(@import._cache.productType[productTypeRef.id])
+          console.log(sampleProductTypeResponse.body.results[0])
+          expect(@import._cache.productType["AGS"]).toEqual sampleProductTypeResponse.body.results[0]
+          done()
+          .catch (err) -> done(err)
+
+


Relax code bank section names character restrictions

--- app/schemas/models/level.coffee
+++ app/schemas/models/level.coffee
@@ -406,8 +406,8 @@
   methodsBankList: c.array {title: 'Methods Bank List'}, c.object {
     properties: {
       name: c.shortString(title: 'Name'),
-      section: c.shortString(title: 'Methods Bank Section', pattern: '^[a-z]+$'),
-      subSection: c.shortString(title: 'Methods Bank Sub-Section', pattern: '^[a-z]+$'),
+      section: c.shortString(title: 'Methods Bank Section', pattern: /^\w[\w ]*$/),
+      subSection: c.shortString(title: 'Methods Bank Sub-Section', pattern: /^\w[\w ]*$/),
       componentName: c.shortString(title: 'Level Component Name', description: 'Level Component to use for documentation in case there are multiple components with same property\'s documentation'),
     }
   }


added minified files to imagemin exclude

--- app/templates/_gruntfile.coffee
+++ app/templates/_gruntfile.coffee
@@ -224,7 +224,7 @@
         files: [
           expand: true
           cwd: "<%%= yeoman.dist %>/images"
-          src: "{,*/}*.{png,jpg,jpeg}"
+          src: ["{,*/}*.{png,jpg,jpeg}", "!*min*"]
           dest: "<%%= yeoman.dist %>/images"
         ]

@@ -263,7 +263,7 @@
           dot: true
           cwd: "<%%= yeoman.app %>"
           dest: "<%%= yeoman.dist %>"
-          src: ["*.{ico,png,txt}", ".htaccess"]
+          src: ["*.{ico,txt}", ".htaccess"]
         ,
           "<%%= yeoman.dist %>/scripts/jquery.min.js": "<%%= yeoman.app %>/bower_components/jquery/jquery.min.js"
         ]


Better pseudocode, might need server graph funcs

--- views/coffeescript/filters.coffee
+++ views/coffeescript/filters.coffee
@@ -54,16 +54,16 @@
   # TODO, pseudocode:
   nodeData = appContext.getNodeData()
   for rel in getRels(node, nodeData)
-    if relsToHide == "all" || shouldHide(rel, relsToHide)
+    if relsToHide == "hide all" || shouldHide(rel, relsToHide)
       hideRel(rel)
       for relatedNode in getRelatedNode(node, rel, nodeData)
-        hideNode(relatedNode)
-        if (!existsPathBetweenNodesNotContainingRel(relatedNode, node, rel, nodeData)
-          hideSubGraph(relatedNode, "all")
+        if (!areConnected(relatedNode, node, nodeData, rel /*ignore this one*/))
+          hideNode(relatedNode)
+          hideSubGraph(relatedNode, "hide all", appContext)

-existsPathBetweenNodesNotContainingRel = (nodeA, nodeB, rel, nodeData) ->
-
+connectionCount = (nodeA, nodeB, nodeData) ->
+  # Yikes...

 getRels = (node, nodeData) ->
   (rel for rel in nodeData.rels when rel.data.end_node == node.id || rel.data.start_node == node.id)


Still trying to get map to work

Still trying to get map to work

--- src/ubret/Map.coffee
+++ src/ubret/Map.coffee
@@ -69,7 +69,7 @@
         s: s

       url = convertTileUrl(tilePoint.x, tilePoint.y, 1, zoom)
-      return "/images/tiles/#{spectrum}/#{url.src}.jpg"
+      return "/images/tiles/#{spectrum}/#{zoom}/#{url.src}.jpg"

     @layer.addTo @map


Test constructor

Test constructor

--- test/src/trix/text_view_test.coffee
+++ test/src/trix/text_view_test.coffee
@@ -2,6 +2,16 @@
 #= require fixtures

 module "Trix.TextView"
+
+
+test "#constructor", ->
+  element = document.createElement("div")
+  new Trix.TextView element, fixture("plain")
+
+  equal element.getAttribute("contenteditable"), "true", "element is contenteditable"
+  equal element.getAttribute("autocorrect"), "off", "element has autocorrect off"
+  equal element.getAttribute("spellcheck"), "false", "element has spellcheck off"
+  equal element.trixPosition, 0, "element has a trixPosition property"

 test "#createElementsForText", ->


fix layer tests

fix layer tests

--- testSpecApp/app/specs/layers.coffee
+++ testSpecApp/app/specs/layers.coffee
@@ -5,32 +5,27 @@
       setTimeout done, 500
       # to solve iOS issue of trying to push when previous push is still under way

-  pushAndPopGoogle = (done) ->
-    googleView = new steroids.views.WebView "http://www.google.com"
-
-    steroids.layers.push {
-      view: googleView
-    }, {
-      onSuccess: ->
-        setTimeout ->
-          steroids.layers.pop {},
-            onSuccess: ->
-              done()
-            onFailure: (error) ->
-              done new Error "could not pop view: " + error.message
-        , 500
-      onFailure: (error) ->
-        done new Error "could not push view: " + error.message
-    }
-
-  # -- tests below --
-
   it "should be defined", ->
     steroids.layers.should.be.defined

   describe "pop & push", ->
     it "should push and pop a view", (done) ->
-      pushAndPopGoogle(done)
+      googleView = new steroids.views.WebView "http://www.google.com"
+
+      steroids.layers.push {
+        view: googleView
+      }, {
+        onSuccess: ->
+          setTimeout ->
+            steroids.layers.pop {},
+              onSuccess: ->
+                done()
+              onFailure: (error) ->
+                done new Error "could not pop view: " + error.message
+          , 500
+        onFailure: (error) ->
+          done new Error "could not push view: " + error.message
+      }

   describe "layers.on", ->
     it "should log 4 layer events in total when pushing and popping a layer", (done)->
@@ -43,4 +38,20 @@
       steroids.layers.on "didchange", ->
         eventsCount++

-      pushAndPopGoogle(done)+      googleView = new steroids.views.WebView "http://www.google.com"
+
+      steroids.layers.push {
+        view: googleView
+      }, {
+        onSuccess: ->
+          setTimeout ->
+            steroids.layers.pop {},
+              onSuccess: ->
+                eventsCount.should.equal 4
+                done()
+              onFailure: (error) ->
+                done new Error "could not pop view: " + error.message
+          , 500
+        onFailure: (error) ->
+          done new Error "could not push view: " + error.message
+      }


:memo: fix ::onDidChangeActivePaneItem docs

Refs #4946

--- src/workspace.coffee
+++ src/workspace.coffee
@@ -203,8 +203,7 @@
   # Essential: Invoke the given callback when the active pane item changes.
   #
   # * `callback` {Function} to be called when the active pane item changes.
-  #   * `event` {Object} with the following keys:
-  #     * `activeItem` The active pane item.
+  #   * `item` The active pane item.
   #
   # Returns a {Disposable} on which `.dispose()` can be called to unsubscribe.
   onDidChangeActivePaneItem: (callback) -> @paneContainer.onDidChangeActivePaneItem(callback)


Optimized Model.clone code

Optimized Model.clone code

--- src/Model.coffee
+++ src/Model.coffee
@@ -107,13 +107,11 @@

 	# Creates a clone of this model.
 	# Extend to properly implement deep cloning where needed.
-	# `baseModel` can be used when extending to provide a base model object to clone into.
-	clone: (baseModel = null) ->
-		baseModel or= new Model
-		baseModel.id @id()
-		@each (key, val) ->
-			baseModel.set key, val
-		return baseModel
+	# `base` can be used when extending to provide a base model object to clone into.
+	clone: (base = null) ->
+		base or= new Model
+		base.set @
+		return base

 	# Serializes the model for persistent storage.
 	# By default this returns a JSON object, but feel free to extend.


add brain list methods

add brain list methods

--- src/brain.coffee
+++ src/brain.coffee
@@ -13,7 +13,6 @@
     @data =
       users: {}
       _private: {}
-      _hprivate: {}

     @autoSave = true
     @ready = Q(@)
@@ -22,7 +21,84 @@
   #
   # Returns promise for object
   dump: ->
-    @getall()
+    Q(@data)
+
+  # Public: get the length of the list stored at `key`
+  #
+  # Returns int
+  llen: (key) ->
+    Q(@_private[@key key].length)
+
+  # Public: set the list value at the specified index
+  #
+  # Returns promise
+  lset: (key, index, value) ->
+    @_private[@key key][index] = @serialize value
+    Q(@)
+
+  # Public: insert a value into the list before or after the pivot element.
+  #
+  # Returns promise
+  linsert: (key, placement, pivot, value) ->
+    key = @key key
+    if @_private[key] isnt undefined
+      index = _.find @_private[key], pivot
+
+      if index
+        @_private[key].splice 0, placement is 'AFTER' ? index + 1 : index, @serialize(value)
+
+    Q(@)
+
+  # Public: push a new value onto the left-side of the list
+  #
+  # Returns promise
+  lpush: (key, value) ->
+    key = @key key
+    if @_private[key] is undefined
+      @_private[key] = []
+
+    @_private[key].unshift(@serialize value)
+    Q(@)
+
+  # Public: push a new value onto the right-side of the list
+  #
+  # Returns promise
+  rpush: (key, value) ->
+    key = @key key
+    if @_private[key] is undefined
+      @_private[key] = []
+
+    @_private[key].push(@serialize value)
+    Q(@)
+
+  # Public: pop a value off of the left-side of the list
+  #
+  # Returns promise for list item
+  lpop: (key) ->
+    Q(@deserialize(@_private[@key key].shift()))
+
+  # Public: pop a value off of the right-side of the list
+  #
+  # Returns promise for list item
+  rpop: (key) ->
+    Q(@deserialize(@_private[@key key].pop()))
+
+  # Public: get a list item by index
+  #
+  # Returns promise for list item
+  lindex: (key, index) ->
+    Q(@deserialize(@_private[@key key][index]))
+
+  # Public: get a slice of the list
+  #
+  # Returns promise for array
+  lrange: (key, start, end) ->
+    key = @key key
+
+    if end < 0
+      end = @_private[key].length + end
+
+    Q(_.map(@_private[key].slice(start, end + 1), @deserialize.bind(@)))

   # Public: get all the keys, optionally restricted to keys prefixed with `searchKey`
   #
@@ -30,13 +106,6 @@
   keys: (searchKey = '') ->
     searchKey = @key searchKey
     Q(_.map(_.filter(_.keys(@data._private), (key) -> key.indexOf searchKey is 0), @unkey.bind(@)))
-
-  # Public: get all values as an object, optionally restricted to keys prefixed with `searchKey`
-  #
-  # Returns promise for object
-  getall: (searchKey = '') ->
-    searchKey = @key searchKey
-    Q(_.transform(@data._private, (result, key, val) => result[@unkey key] = value if key.indexOf searchKey is 0))

   # Public: transform a key from internal brain key, to user-facing key
   #
@@ -82,41 +151,41 @@
   #
   # Returns promise for array.
   hkeys: (table) ->
-    Q(_.keys(@_hprivate[@key table] or {}))
+    Q(_.keys(@_private[@key table] or {}))

   # Public: Get all the values for the given hash table name
   #
   # Returns promise for array.
   hvals: (table) ->
-    Q(_.mapValues(@_hprivate[@key table] or {}, @deserialize.bind(@)))
+    Q(_.mapValues(@_private[@key table] or {}, @deserialize.bind(@)))

   # Public: Set a value in the specified hash table
   #
   # Returns promise for the value.
   hset: (table, key, value) ->
     table = @key table
-    @_hprivate[table] = @_hprivate[table] or {}
-    @_hprivate[table][key] = @serialize value
+    @_private[table] = @_private[table] or {}
+    @_private[table][key] = @serialize value
     Q(@)

   # Public: Get a value from the specified hash table.
   #
   # Returns: promise for the value.
   hget: (table, key) ->
-    Q(@deserialize @_hprivate[@key table][key])
+    Q(@deserialize @_private[@key table][key])

   # Public: Delete a field from a hash table
   #
   # Returns promise
   hdel: (table, key) ->
-    delete @_hprivate[@key table][key]
+    delete @_private[@key table][key]
     Q(@)

   # Public: Get the whole hash table as an object.
   #
   # Returns: promise for object.
   hgetall: (table) ->
-    Q(_.clone(_.mapValues(@_hprivate[@key table], @deserialize.bind @)))
+    Q(_.clone(_.mapValues(@_private[@key table], @deserialize.bind @)))

   # Public: increment the hash value by num atomically
   #


Fix indentation error from merge

Fix indentation error from merge

--- src/docco.coffee
+++ src/docco.coffee
@@ -124,7 +124,7 @@
     if output is ''
       codeFragments = (htmlEscape section.codeText for section in sections)
     else
-    codeFragments = output.split language.codeSplitHtml
+      codeFragments = output.split language.codeSplitHtml
     docsFragments = showdown.makeHtml(docs).split language.docsSplitHtml

     for section, i in sections


`process.ARGV` has been removed. Closes #103.

--- src/docco.coffee
+++ src/docco.coffee
@@ -215,7 +215,7 @@

 # Run the script.
 # For each source file passed in as an argument, generate the documentation.
-sources = process.ARGV.sort()
+sources = process.argv.sort()
 if sources.length
   ensure_directory 'docs', ->
     fs.writeFile 'docs/docco.css', docco_styles


View disposal: delete _callbacks property

--- coffee/chaplin/views/view.coffee
+++ coffee/chaplin/views/view.coffee
@@ -367,8 +367,8 @@
     disposed: false

     dispose: =>
+      #console.debug 'View#dispose', this, 'disposed?', @disposed
       return if @disposed
-      #console.debug 'View#dispose', this

       # Dispose subviews
       view.dispose() for view in @subviews
@@ -379,18 +379,20 @@
       # Unbind all model handlers
       @modelUnbindAll()

-      # Remove all event handlers
+      # Remove all event handlers on this module
       @off()

       # Remove the topmost element from DOM. This also removes all event
       # handlers from the element and all its children.
       @$el.remove()

-      # Remove element references, options and model/collection references
+      # Remove element references, options,
+      # model/collection references and subview lists
       properties = [
         'el', '$el',
         'options', 'model', 'collection',
-        'subviews', 'subviewsByName'
+        'subviews', 'subviewsByName',
+        '_callbacks'
       ]
       delete this[prop] for prop in properties


add todo

add todo

--- app/components/eui-poplist.coffee
+++ app/components/eui-poplist.coffee
@@ -272,6 +272,9 @@
     offset = element.offset()

     # set a reasonable min-width on the poplist before we caclulate its actual size
+    # TODO: Min-width is no longer sufficient because we use list-view which uses
+    # absolute positioning. This means the popup can no longer get pushed wider
+    # by its children. Need a new approach.
     elementWidthMinuspoplistPadding = element.width() - parseFloat(poplistElement.css('paddingLeft')) - parseFloat(poplistElement.css('paddingRight'))
     poplistElement.css('min-width', elementWidthMinuspoplistPadding)


fix radio_group

fix radio_group

--- app/components/radio_group.coffee
+++ app/components/radio_group.coffee
@@ -29,7 +29,7 @@
       val = utils.serialize val
       ref = @where(record: {value: val})
       if ref.length
-        @select_item ref[0], true
+        @select_item ref[0]
     else
       @input.value()


Annotate project and stage

Annotate project and stage

--- app/controllers/classifier.coffee
+++ app/controllers/classifier.coffee
@@ -619,8 +619,11 @@
       if @classification.subject.metadata.training[0].type in ['lensing cluster', 'lensed quasar', 'lensed galaxy']
         @trigger 'addToTalk', @talkIds['My Simulations'], @classification.subject.zooniverse_id

-    # Record language
-    @classification.annotate {language: localStorage.preferredLanguage}
+    # Record various bits
+    @classification.annotate
+      language: localStorage.preferredLanguage
+      project: "VICS82"
+      stage: 1

     @classification.send()


Not using jQuery.data()

Not using jQuery.data()

--- app/controllers/guide_page.coffee
+++ app/controllers/guide_page.coffee
@@ -14,9 +14,7 @@

   show: (e) =>
     $(e.currentTarget).addClass('show').siblings('li').removeClass('show')
-
-    key = $(e.currentTarget).data('type')
-    $('section[data-type="'+key+'"]').addClass('show').siblings('section').removeClass('show')
+    $('section[data-type="' + e.currentTarget.dataset.type + '"]').addClass('show').siblings('section').removeClass('show')

     @el.scroll() # trick lazyload


Existential check incase there are no artworks

--- apps/feature/client/view.coffee
+++ apps/feature/client/view.coffee
@@ -67,7 +67,7 @@

     @artworks = saleFeaturedSet.get('data')

-    @filterView.setArtworks @artworks
+    @filterView?.setArtworks @artworks

     @artworks.on 'filterSort', =>
       @$('#feature-artworks').html ''
@@ -78,7 +78,7 @@
       @appendArtworks @artworks
       @renderArtistList @artworks if saleFeaturedSet and saleFeaturedSet.get('display_artist_list')

-    @filterView.trigger 'doneFetching'
+    @filterView?.trigger 'doneFetching'

   setupArtworkFiltering: (saleArtworksCollection) ->
     @$('#feature-artworks').before filterTemplate()


evaluator: coffeescriptify

evaluator: coffeescriptify

--- src/evaluator.coffee
+++ src/evaluator.coffee
@@ -73,12 +73,12 @@
 class Conjunction extends InfixExpression
   constructor: (node) ->
     super node, (left,  right) ->
-      left && right
+      left and right

 class Disjunction extends InfixExpression
   constructor: (node) ->
     super node, (left,  right) ->
-      left || right
+      left or right

 class Comparison extends InfixExpression
   constructor: (node) ->
@@ -93,9 +93,9 @@
   compare: (left, right) ->
     switch @comparator
       when '=', '=='
-        left == right
+        left is right
       when '!=', '<>'
-        left != right
+        left isnt right
       when '<'
         left < right
       when '>'
@@ -144,14 +144,14 @@
     @func vals

 funcs = {
-  'and': (p) -> p.reduce (a, b) -> a && b,
-  'or': (p) -> p.reduce (a, b) -> a || b,
+  'and': (p) -> p.reduce (a, b) -> a and b
+  'or': (p) -> p.reduce (a, b) -> a or b
   'not': (p) -> !p[0]
   'if': (p) -> if p[0] then p[1] else p[2]
   'isnull': (p) -> !p[0]?
   'isblank': (p) -> !p[0]? or p[0] is ''
   'nullvalue': (p) -> if p[0]? then p[0] else p[1]
-  'blankvalue': (p) -> if p[0]? and p[0] is not '' then p[0] else p[1]
+  'blankvalue': (p) -> if p[0]? and p[0] isnt '' then p[0] else p[1]
 }

 evaluate = (formula, data) ->


Age module

Age module

--- src/expirable.coffee
+++ src/expirable.coffee
@@ -40,7 +40,7 @@
   self.bind "update", (dt) ->
     if I.fadeOut
       I.alpha = startingAlpha * (1 - (I.age / I.duration))
-
+
     if I.duration != -1 && I.age >= I.duration
       I.active = false


Fixing printing issue

Fixing printing issue

--- scripts/shippingLaneTab.coffee
+++ scripts/shippingLaneTab.coffee
@@ -62,7 +62,6 @@
     isobathPercentChange = Math.round((Math.abs(isobathChange) / existingIsobathIntersection) * 100)
     existingLength = 122.75
     length = @model.get('geometry').features[0].attributes.Shape_Length / 5048
-    window.graphics = @app.projecthomepage.getLayer(@model).getGraphics(@model)
     percentChange = Math.abs(((existingLength - length) / existingLength) * 100)
     lengthIncreased = existingLength - length < 0
     lengthChangeClass = if lengthIncreased then 'positive' else 'negative'


changed to update the rootInfoFile only if the algo is correctly generated

--- app/routes/algorithmRouter.coffee
+++ app/routes/algorithmRouter.coffee
@@ -184,7 +184,6 @@
     #create bash script
     DockerManagement.createBashScript(req.body, '/data/executables/' + route)
     #update servicesFile
-    AlgorithmManagement.updateRootInfoFile(req.body, route)
     AlgorithmManagement.updateStatus(identifier, 'creating', '/' + route)
     #create a tar from zip
     response =
@@ -248,6 +247,7 @@
               if(error)
                 AlgorithmManagement.updateStatus(identifier, 'error', null, error.statusMessage)
               else
+                AlgorithmManagement.updateRootInfoFile(req.body, route)
                 AlgorithmManagement.createInfoFile(req.body, '/data/json/' + route)
             )
     ))


Arrange brush strokes around a sphere

Arrange brush strokes around a sphere

--- assets/script/StrokeMesh.coffee
+++ assets/script/StrokeMesh.coffee
@@ -4,7 +4,7 @@
 module.exports = class StrokeMesh
 	constructor: (scene) ->
 		@nStrokes =
-			1000
+			10000

 		uniforms =
 			bright:
@@ -37,11 +37,23 @@
 		@strokeGeometry =
 			new three.Geometry

+		@circleCenter =
+			new three.Vector3 0, 0, 0
+
+		@circleRadius =
+			2
+
 		for idx in [0...@nStrokes]
-			p = ->
-				Math.random() * 20 - 10
+			randCoord = ->
+				Math.random() * 2 - 1

-			@strokeGeometry.vertices.push new three.Vector3 p(), p(), p()
+			position =
+				new three.Vector3 randCoord(), randCoord(), randCoord()
+			position.normalize()
+			position.multiplyScalar @circleRadius
+			position.add @circleCenter
+
+			@strokeGeometry.vertices.push position

 		@strokeSystem =
 			new three.ParticleSystem @strokeGeometry, @material


tweaks to DataAdapter

tweaks to DataAdapter

--- server/lib/data_adapter.coffee
+++ server/lib/data_adapter.coffee
@@ -24,8 +24,10 @@
       allow4xx: false

     requestApi = @apiDefaults(req)
-    debug('request: %s', inspect(requestApi))
+    start = new Date().getTime()
     request requestApi, (err, response, body) =>
+      end = new Date().getTime()
+      debug('%s %s %s %sms', requestApi.method.toUpperCase(), requestApi.url, response.statusCode, end - start)
       if options.convertErrorCode
         err ||= @getErrForResponse(response, {allow4xx: options.allow4xx})
       if ~(response.headers['content-type'] || '').indexOf('application/json')
@@ -44,6 +46,7 @@
     urlOpts.pathname = req.path || req.pathname || req.url?.split('?')[0]

     _.defaults api,
+      method: 'GET'
       json: req.body
       url: url.format(urlOpts)


HTTP interface for precense.

HTTP interface for precense.

--- server/lib/server/index.coffee
+++ server/lib/server/index.coffee
@@ -25,7 +25,37 @@
       middleware : (name,callback) -> koding.disconnect callback
       middlewareTimeout : 5000
     librato: KONFIG.librato
-
+
+  # Services
+  services =
+    webterm: 0
+    auth: 0
+    web: 0
+    social: 0
+
+  # Presences
+  koding = require './bongo'
+  koding.connect ->
+    koding.monitorPresence
+      join: (serviceKey) ->
+        if serviceKey.indexOf('webterm') > -1
+          services.webterm++
+        else if serviceKey.indexOf('auth') > -1
+          services.auth++
+        else if serviceKey.indexOf('web') > -1
+          services.web++
+        else if serviceKey.indexOf('social') > -1
+          services.social++
+      leave: (serviceKey) ->
+        if serviceKey.indexOf('webterm') > -1
+          services.webterm--
+        else if serviceKey.indexOf('auth') > -1
+          services.auth--
+        else if serviceKey.indexOf('web') > -1
+          services.web--
+        else if serviceKey.indexOf('social') > -1
+          services.social--
+
   {extend} = require 'underscore'
   express = require 'express'
   Broker = require 'broker'
@@ -122,6 +152,13 @@
         throw err if err
         res.send data

+  app.get "/-/presence/:service", (req, res) ->
+    {service} = req.params
+    if services[service] and services[service] > 0
+      res.send 200
+    else
+      res.send 404
+
   app.get "/-/status/:event/:kiteName",(req,res)->
     # req.params.data


Fix error message save

Fix error message save

--- server/models/konnector.coffee
+++ server/models/konnector.coffee
@@ -116,10 +116,12 @@
         @runImport values, next
     , (err, notifContents) =>
         if err
+            errMessage = if err.message? then err.message else err.toString()
             data =
                 isImporting: false
                 lastImport: new Date()
-                importErrorMessage: err.message.replace /<[^>]*>/ig, ''
+                importErrorMessage: errMessage.replace(/<[^>]*>/ig, '')
+
         else
             data =
                 isImporting: false


Specified some defaults. Can be overridden in config file.

--- settings/language-latex2e.cson
+++ settings/language-latex2e.cson
@@ -3,7 +3,10 @@
     'commentStart': '% '
     'increaseIndentPattern': '^\\s*(\\\\)begin\\{[^(document)]\\w*\\}'
     'decreaseIndentPattern': '^\\s*(\\\\)end\\{\\w*\\}'
-    'tablength': 2
+    'tabLength': 4
+    'softWrap': true
+    'preferredLineLength': 90
+    'scrollPastEnd': true

 '.text.tex.latex.tikz':
   'editor':


Don't blow away things in scope #closurefail.

--- script/worker.coffee
+++ script/worker.coffee
@@ -1,5 +1,5 @@
 sys         = require('sys')
-spawn       = require('child_process').spawn
+Spawn       = require('child_process').spawn
 Repository  = require('../models/repository').Repository
 Build       = require('../models/build').Build
 Resque      = require('../config/resque').Connection
@@ -47,31 +47,31 @@
         name = 'mkdir'
         workingDir = ['/tmp/', 'dripio', repository.ownerName, repository.name, Date.now()].join('_')
         console.log "making directory ["+workingDir+"]..."
-        cmds[name] = spawn('mkdir',['-vp',workingDir])
+        cmds[name] = Spawn('mkdir',['-vp',workingDir])
         cmdOut.bind(name, spawnClone)

       spawnClone = ->
         name = 'clone'
         console.log "cloning ["+repository.url+"]..."
-        cmds[name] = spawn('git', ['clone', repository.url, workingDir], { cwd: workingDir, setsid: false })
+        cmds[name] = Spawn('git', ['clone', repository.url, workingDir], { cwd: workingDir, setsid: false })
         cmdOut.bind(name, spawnCheckout)

       spawnCheckout = ->
         name = 'checkout'
         console.log "checkout ["+build.branch+"]..."
-        cmds[name] = spawn('git', ['checkout',build.branch], {cwd: workingDir, setsid: false})
+        cmds[name] = Spawn('git', ['checkout',build.branch], {cwd: workingDir, setsid: false})
         cmdOut.bind(name, spawnNpmInstall)

       spawnNpmInstall = ->
         name = 'npm_install'
         console.log("running npm install...")
-        cmds[name] = spawn('npm',['install'], {cwd: workingDir})
+        cmds[name] = Spawn('npm',['install'], {cwd: workingDir})
         cmdOut.bind(name, spawnNpmTest)

       spawnNpmTest = ->
         name = 'npm_test'
         console.log("running npm test...")
-        cmds[name] = spawn('npm',['test'], {cwd: workingDir})
+        cmds[name] = Spawn('npm',['test'], {cwd: workingDir})
         cmdOut.bind(name, buildFinish)

       buildFinish = ->
@@ -85,7 +85,7 @@
           if err
             throw err

-        cmds[name] = spawn('rm',['-vrf', workingDir])
+        cmds[name] = Spawn('rm',['-vrf', workingDir])
         cmdOut.bind(name)

       cmdOut =


Fix in Transaction

Fix in Transaction

--- app/src/wallet/transaction.coffee
+++ app/src/wallet/transaction.coffee
@@ -91,127 +91,11 @@
   setHash: (hash) -> @hash = hash

   serialize: ->
-    amount: @amount.toNumber(),
+    amount: @amount.toSatoshiNumber(),
     address: @receiverAddress,
-    fee: @fees.toNumber(),
+    fee: @fees.toSatoshiNumber(),
     hash: @hash,
     raw: @getSignedTransaction()
-
-  # Retrieve information that need to be confirmed by the user.
-  # @return [Object]
-  #   @option [Integer] validationMode
-  #   @option [Object, undefined] amount
-  #     @option [String] text
-  #     @option [Array<Integer>] indexes
-  #   @option [Object] recipientsAddress
-  #     @option [String] text
-  #     @option [Array<Integer>] indexes
-  #   @option [String] validationCharacters
-  #   @option [Boolean] needsAmountValidation
-  # getValidationDetails: ->
-  #   details =
-  #     validationMode: @_validationMode
-  #     recipientsAddress:
-  #       text: @recipientAddress
-  #       indexes: @_resumeData.indexesKeyCard.match(/../g)
-  #     validationCharacters: (@recipientAddress[index] for index in @_resumeData.indexesKeyCard.match(/../g))
-  #     needsAmountValidation: false
-
-  #   # ~> 1.4.13 need validation on amount
-  #   if @dongle.getIntFirmwareVersion() < ledger.dongle.Firmware.V1_4_13
-  #     stringifiedAmount = @amount.toString()
-  #     stringifiedAmount = _.str.lpad(stringifiedAmount, 9, '0')
-  #     # Split amount in integer and decimal parts
-  #     integerPart = stringifiedAmount.substr(0, stringifiedAmount.length - 8)
-  #     decimalPart = stringifiedAmount.substr(stringifiedAmount.length - 8)
-  #     # Prepend to validationCharacters first digit of integer part,
-  #     # and 3 first digit of decimal part only if not empty.
-  #     amountChars = [integerPart.charAt(integerPart.length - 1)]
-  #     if decimalPart isnt "00000000"
-  #       amountChars.concat decimalPart.substring(0,3).split('')
-  #     details.validationCharacters = amountChars.concat(details.validationCharacters)
-  #     # Compute amount indexes
-  #     firstIdx = integerPart.length - 1
-  #     lastIdx = if decimalPart is "00000000" then firstIdx else firstIdx+3
-  #     detail.amount =
-  #       text: stringifiedAmount
-  #       indexes: [firstIdx..lastIdx]
-  #     details.needsAmountValidation = true
-
-  #   return details
-
-  getValidationDetails: () ->
-    indexes = []
-    if @_validationMode is ledger.wallet.Transaction.ValidationModes.SECURE_SCREEN
-      numberOfCharacters = parseInt(@_resumeData.indexesKeyCard.substring(0, 2), 16)
-      indexesKeyCard = @_resumeData.indexesKeyCard.substring(2, numberOfCharacters * 2 + 2)
-    else
-      indexesKeyCard = @_resumeData.indexesKeyCard
-    amount = ''
-    if @dongle.getIntFirmwareVersion() < ledger.dongle.Firmware.V1_4_13
-      stringifiedAmount = @amount.toString()
-      stringifiedAmount = _.str.lpad(stringifiedAmount, 9, '0')
-      decimalPart = stringifiedAmount.substr(stringifiedAmount.length - 8)
-      integerPart = stringifiedAmount.substr(0, stringifiedAmount.length - 8)
-      firstAmountValidationIndex = integerPart.length - 1
-      lastAmountValidationIndex = firstAmountValidationIndex
-      if decimalPart isnt "00000000"
-        lastAmountValidationIndex += 3
-
-    while indexesKeyCard.length >= 2
-      index = indexesKeyCard.substring(0, 2)
-      indexesKeyCard = indexesKeyCard.substring(2)
-      indexes.push parseInt(index, 16)
-
-  # @param [Array<Object>] inputs
-  # @param [String] changePath
-  # @param [Function] callback
-  # @return [Q.Promise]
-  prepare: (callback=undefined) ->
-    if not @amount? or not @fees? or not @recipientAddress?
-      Errors.throw('Transaction must me initialized before preparation')
-    d = ledger.defer(callback)
-    @dongle.createPaymentTransaction(@_btInputs, @_btcAssociatedKeyPath, @changePath, @recipientAddress, @amount, @fees)
-    .then (@_resumeData) =>
-      @_validationMode = @_resumeData.authorizationRequired
-      @authorizationPaired = @_resumeData.authorizationPaired
-      d.resolve()
-    .fail (error) =>
-      d.rejectWithError(Errors.SignatureError)
-    .done()
-    d.promise
-
-  # @param [String] validationKey 4 chars ASCII encoded
-  # @param [Function] callback
-  # @return [Q.Promise]
-  validateWithPinCode: (validationPinCode, callback=undefined) -> @_validate(validationPinCode, callback)
-
-  # @param [String] validationKey 4 chars ASCII encoded
-  # @param [Function] callback
-  # @return [Q.Promise]
-  validateWithKeycard: (validationKey, callback = null) -> @_validate(("0#{char}" for char in validationKey).join(''), callback)
-
-  # @param [String] validationKey 4 chars ASCII encoded
-  # @param [Function] callback
-  # @return [Q.Promise]
-  _validate: (validationKey, callback=undefined) ->
-    if not @_resumeData? or not @_validationMode?
-      Errors.throw('Transaction must me prepared before validation')
-    d = ledger.defer(callback)
-    @dongle.createPaymentTransaction(
-      @_btInputs, @_btcAssociatedKeyPath, @changePath, @recipientAddress, @amount, @fees,
-      undefined, # Default lockTime
-      undefined, # Default sigHash
-      validationKey,
-      resumeData
-    )
-    .then (@_transaction) =>
-      @_isValidated = yes
-      _.defer => d.resolve()
-    .fail (error) =>
-      _.defer => d.rejectWithError(Errors.SignatureError, error)
-    .done()
-    d.promise

   # Retrieve information that need to be confirmed by the user.
   # @return [Object]
@@ -231,6 +115,9 @@
         text: @recipientAddress
         indexes: (parseInt(i,16) for i in @_resumeData.indexesKeyCard.match(/../g))
       needsAmountValidation: false
+    if @_validationMode is ledger.wallet.Transaction.ValidationModes.SECURE_SCREEN
+      length = details.recipientsAddress.indexes.shift()
+      details.recipientsAddress.indexes = details.recipientsAddress.indexes.slice(0, length)
     details.validationCharacters = (@recipientAddress[idx] for idx in details.recipientsAddress.indexes)

     # ~> 1.4.13 need validation on amount
@@ -273,7 +160,7 @@
       d.rejectWithError(Errors.SignatureError)
     .done()
     d.promise
-
+
   # @param [String] validationKey 4 chars ASCII encoded
   # @param [Function] callback
   # @return [Q.Promise]
@@ -282,7 +169,7 @@
   # @param [String] validationKey 4 chars ASCII encoded
   # @param [Function] callback
   # @return [Q.Promise]
-  validateWithKeycard: (validationKey, callback = null) -> @_validate(("0#{char}" for char in validationKey).join(''), callback)
+  validateWithKeycard: (validationKey, callback=undefined) -> @_validate(("0#{char}" for char in validationKey).join(''), callback)

   # @param [String] validationKey 4 chars ASCII encoded
   # @param [Function] callback
@@ -297,13 +184,12 @@
       undefined, # Default sigHash
       validationKey,
       @_resumeData
-    )
-    .then (@_transaction) =>
+    ).then( (@_transaction) =>
       @_isValidated = yes
       _.defer => d.resolve(@)
-    .fail (error) =>
+    ).catch( (error) =>
       _.defer => d.rejectWithError(Errors.SignatureError, error)
-    .done()
+    ).done()
     d.promise

   ###


ConfirmEmail: obsolete search objects are removed and search method is added

--- client/Dashboard/AppView.coffee
+++ client/Dashboard/AppView.coffee
@@ -65,6 +65,9 @@
     @searchWrapper.addSubView @searchIcon
     @header.addSubView @searchWrapper

+    @on "groupSettingsUpdated", (group)->
+      @setData group
+      @createTabs()

   setListeners:->

@@ -101,9 +104,21 @@

       @navController.replaceAllItems navItems
       @nav.emit "ready"
+      @emit "ready"

   pistachio:->
     """
       {{> @nav}}
       {{> @tabs}}
     """
+
+  search: (searchValue)->
+    if @tabs.getActivePane().name is 'Invitations'
+      pane = @tabs.getActivePane()
+    else
+      pane = @tabs.getPaneByName "Members"
+      @tabs.showPane pane
+    {mainView} = pane
+    return unless mainView
+    mainView.emit 'SearchInputChanged', searchValue
+


Router: minor bug fix

Router: minor bug fix

--- client/Main/kodingrouter.coffee
+++ client/Main/kodingrouter.coffee
@@ -60,7 +60,7 @@
         KD.utils.defer => @handleRoute route, options

     {entryPoint} = options
-    if entryPoint?.slug? and entryPoint.type is "group"
+    if entryPoint?.slug and entryPoint.type is "group"
       entrySlug = "/" + entryPoint.slug
       # if incoming route is prefixed with groupname or entrySlug is the route
       # also we dont want koding as group name


moved get_user_id call to Meteor.startup to fix null user problem.

--- client/coffeescript/main.coffee
+++ client/coffeescript/main.coffee
@@ -6,11 +6,6 @@
 Meteor.subscribe( "my_user" )
 Meteor.subscribe( "my_posts" )
 Meteor.subscribe( "assigned_posts" )
-
-# Get User ID
-Meteor.call('get_user_id', (err, res) ->
-  Session.set('user_id', res)
-)

 # Backbone router
 class Router extends Backbone.Router
@@ -26,5 +21,10 @@

 Router = new Router()
 Meteor.startup( ->
+  # Get User ID
+  Meteor.call('get_user_id', (err, res) ->
+    Session.set('user_id', res)
+  )
+
   Backbone.history.start( pushState: true ) #!SUPPRESS no_headless_camel_case
-)+)


Delete for integrations

Delete for integrations

--- app/assets/javascripts/admin/routers/integrations.js.coffee
+++ app/assets/javascripts/admin/routers/integrations.js.coffee
@@ -3,6 +3,7 @@
     "integrations": "index"
     "integrations/new": "new"
     "integrations/:id/edit": "edit"
+    "integrations/delete/:id?confirm=:confirm": "delete"
     "integrations/:id/signup": "signup"

   index: ->
@@ -27,6 +28,18 @@
     view = new Augury.Views.Integrations.Edit(model: integration)
     $("#integration_main").html view.render().el

+  delete: (id, confirm) ->
+    integration = Augury.store_integrations.get id
+    if confirm != 'true'
+      dialog = JST['admin/templates/shared/confirm_delete']
+      $.modal(dialog(klass: 'integrations', warning: 'Are you sure you want to delete this integration?', identifier: id))
+    else
+      $.modal.close()
+      integration.destroy()
+      Augury.store_integrations.remove integration
+      Backbone.history.navigate '/integrations', trigger: true
+      Augury.Flash.notice "The integration has been deleted."
+
   signup: (integration_id) ->
     Augury.update_nav('integrations')


ADD: class uploader-embed

ADD: class uploader-embed

--- app/assets/javascripts/media_lib/inserter_factory.js.coffee
+++ app/assets/javascripts/media_lib/inserter_factory.js.coffee
@@ -71,7 +71,7 @@

     $uploaderVideo = $uploader.find('.js-uploader-video')
     if $uploaderVideo.length
-      $player = $("")
+      $player = $("")
       $uploaderVideo.html $player

 # Стратегия вставки видео-модели


Show current value in edit parameter dialog.

--- app/assets/javascripts/devices.js.coffee
+++ app/assets/javascripts/devices.js.coffee
@@ -174,7 +174,8 @@
       actions += "Refresh"
     else
       if isWritable
-        actions += "Edit"
+        defaultValue = this.getAttribute('value')
+        actions += "Edit"
       actions += "Refresh"

     $(this).prepend("#{actions}")


initial commit for fullscreen API + pointer locking

--- app/assets/javascripts/libs/mouse.coffee
+++ app/assets/javascripts/libs/mouse.coffee
@@ -4,6 +4,9 @@
 		SLOWDOWN_FACTOR = 250

 		buttonDown : false
+
+		# used for mopuse locking in fullscreen mode
+		locked : false

 		lastPosition :
 			x : null
@@ -25,6 +28,7 @@
 			@attachEventHandler "mousemove", @mouseMoved
 			@attachEventHandler "mouseup", @mouseUp
 			@attachEventHandler "mousedown", @mouseDown
+			@attachEventHandler "webkitfullscreenchange", @lockMouse

 		###
 		#Binds a function as callback when X-Position was changed
@@ -46,15 +50,22 @@

 			{ lastPosition, changedCallback } = @

-			if @buttonDown
-				distX = -(evt.pageX - lastPosition.x)
-				distY =   evt.pageY - lastPosition.y
-				changedCallback.x distX/SLOWDOWN_FACTOR if distX isnt 0
-				changedCallback.y distY/SLOWDOWN_FACTOR if distY isnt 0
+			unless @locked
+				if @buttonDown
+					distX = -(evt.pageX - lastPosition.x)
+					distY =   evt.pageY - lastPosition.y
+					changedCallback.x distX/SLOWDOWN_FACTOR if distX isnt 0
+					changedCallback.y distY/SLOWDOWN_FACTOR if distY isnt 0

-			@lastPosition =
-				x : evt.pageX
-				y : evt.pageY
+				@lastPosition =
+					x : evt.pageX
+					y : evt.pageY
+			else
+				if @buttonDown
+					distX = evt.movementX
+					distY = evt.movementY
+					changedCallback.x distX/SLOWDOWN_FACTOR if distX isnt 0
+					changedCallback.y distY/SLOWDOWN_FACTOR if distY isnt 0

 		mouseDown : =>
 			$(@target).css("cursor", "none")
@@ -64,6 +75,12 @@
 			@buttonDown = false
 			$(@target).css("cursor", "auto")

+		lockMouse : =>
+			@lock = true
+
+		unlockMouse : =>
+			@lock = false
+
 		attachEventHandler : (type, func) ->
 			if @target.addEventListener
 				@target.addEventListener type, func, false


Silly user presence bugs...

Silly user presence bugs...

--- app/assets/javascripts/scirate.js.coffee
+++ app/assets/javascripts/scirate.js.coffee
@@ -10,7 +10,7 @@
     'click .collapse': "collapse"

   initialize: ->
-    @$el.addClass('active') if SciRate.scited_by_uid[@$el.attr('data-paper-uid')]
+    @$el.addClass('active') if SciRate.scited_by_uid && SciRate.scited_by_uid[@$el.attr('data-paper-uid')]

   scite: ->
     return SciRate.login() unless SciRate.current_user


increase decimal precision < 0.5

increase decimal precision < 0.5

--- app/assets/javascripts/zvg/column.coffee
+++ app/assets/javascripts/zvg/column.coffee
@@ -274,7 +274,14 @@
       .attr('opacity', 1)

   percentScale: d3.scale.linear().range([0,1])
-  percentFormat: d3.format('.0%')
+  _defaultPercentFormat: d3.format('.0%')
+  _increasedPrecisionPrecentFormat: d3.format('.1%')
+  percentFormat: (n) ->
+    if n < 0.005
+      @_increasedPrecisionPrecentFormat(n)
+    else
+      @_defaultPercentFormat(n)
+
   countFormat: d3.format('.0')

   initializeY: ->


Add .standard-input.full classes to task choice inputs (#3476)

Fixes #621

--- app/classifier/tasks/generic-editor.cjsx
+++ app/classifier/tasks/generic-editor.cjsx
@@ -102,7 +102,7 @@
             choice._key ?= Math.random()

               <AutoSave resource={@props.workflow}>
-                <textarea name="#{@props.taskPrefix}.#{choicesKey}.#{index}.label" value={choice.label} onChange={handleChange} />
+                <textarea name="#{@props.taskPrefix}.#{choicesKey}.#{index}.label" className="standard-input full" value={choice.label} onChange={handleChange} />
               </AutoSave>


Higher priority to Emmet’s Tab key handler

--- keymaps/emmet.cson
+++ keymaps/emmet.cson
@@ -18,10 +18,13 @@
   'alt-cmd-.': 'emmet:select-next-item'
   'alt-cmd-,': 'emmet:select-previous-item'
   'cmd-R': 'emmet:reflect-css-value'
-  'tab': 'emmet:expand-abbreviation-with-tab'
   'ctrl-I': 'emmet:update-image-size'
   'ctrl-D': 'emmet:encode-decode-data-url'
   'ctrl-U': 'emmet:update-tag'
   'cmd-M': 'emmet:merge-lines'
   'cmd-A': 'emmet:wrap-with-abbreviation'
   'cmd-alt-enter': 'emmet:interactive-expand-abbreviation'
+
+# more specificity to Emmet to override Tab key action for snippets plugin
+'.pane .editor:not(.mini)':
+	'tab': 'emmet:expand-abbreviation-with-tab'


fix taskboard tasks

fix taskboard tasks

--- app/coffee/modules/taskboard/main.coffee
+++ app/coffee/modules/taskboard/main.coffee
@@ -330,7 +330,7 @@

     refreshTagsColors: ->
         return @rs.projects.tagsColors(@scope.projectId).then (tags_colors) =>
-            @scope.project.tags_colors = tags_colors
+            @scope.project.tags_colors = tags_colors._attrs

     loadSprint: ->
         return @rs.sprints.get(@scope.projectId, @scope.sprintId).then (sprint) =>


AppController: Make file content empty if we couldn't fetch it for some reason.

--- client/ide/AppController.coffee
+++ client/ide/AppController.coffee
@@ -1214,7 +1214,7 @@
         else if path.indexOf('localfile:/Untitled.txt') is 0
           @openFile file, context.file.content, noop, no
         else
-          file.fetchContents (err, contents) =>
+          file.fetchContents (err, contents = '') =>
             @changeActiveTabView paneType
             @openFile file, contents, noop, no


AppController: Emit PermissionChanged.

AppController: Emit PermissionChanged.

--- client/ide/AppController.coffee
+++ client/ide/AppController.coffee
@@ -1020,8 +1020,11 @@

     @rtm.on 'MapValueChanged', (map, event) =>

-      if      map is @myWatchMap  then @handleWatchMapChange event
-      else if map is @permissions then @handlePermissionsMapChange event
+      if map is @myWatchMap
+        @handleWatchMapChange event
+
+      else if map is @permissions
+        @chat.settingsPane.emit 'PermissionChanged', event

   handleWatchMapChange: (event) ->
@@ -1033,11 +1036,6 @@

     else unless newValue
       @statusBar.emit 'ParticipantUnwatched', property
-
-
-  handlePermissionsMapChange: (event) ->
-
-    @chat.settingsPane.defaultPermission.setValue event.newValue

   handleChange: (change) ->


Every save needs to include ID

Every save needs to include ID

--- client/lanes/models/Base.coffee
+++ client/lanes/models/Base.coffee
@@ -202,6 +202,8 @@
         attrs = {}
         _.extend(attrs, _.pick( this.getAttributes(props:true, true),
             @changeMonitor?.changedAttributes() || {} ) )
+        unless _.isEmpty(attrs) or @isNew()
+            attrs[@idAttribute] = this.getId()
         attrs

     # returns data to save to server.  If options.saveAll is true,


fix sortable bug on node

fix sortable bug on node

--- BackofficeBundle/Resources/public/coffee/page/orderNode.coffee
+++ BackofficeBundle/Resources/public/coffee/page/orderNode.coffee
@@ -4,20 +4,22 @@
     cancel: ".node-unsortable"
     delay: 150
     distance: 5
+    activate: (event, ui)->
+      @target = $(event.originalEvent.target)
     beforeStop: (event, ui)->
-      smartConfirm(
-        'fa-sort-amount-asc',
-        $(this).data('confirm-title') + ' "' + $(event.originalEvent.target).text() + '"',
-        $(event.originalEvent.target).closest('ul.node-connectedSortable').data('confirm-text')
-        callBackParams:
-          sortableElement: $(this)
-        noCallback: (params) ->
-          params.sortableElement.sortable("cancel")
-        yesCallback: (params) ->
-          sendSortedNodeData(params.sortableElement)
-          sendSortedNodeData($(ui.item).parent()) if params.sortableElement[0] != $(ui.item).parent()[0]
-      )
-
+      if(@target.is($(event.originalEvent.target)))
+        smartConfirm(
+          'fa-sort-amount-asc',
+          $(this).data('confirm-title') + ' "' + $(event.originalEvent.target).text() + '"',
+          $(event.originalEvent.target).closest('ul.node-connectedSortable').data('confirm-text')
+          callBackParams:
+            sortableElement: $(this)
+          noCallback: (params) ->
+            params.sortableElement.sortable("cancel")
+          yesCallback: (params) ->
+            sendSortedNodeData(params.sortableElement)
+            sendSortedNodeData($(ui.item).parent()) if params.sortableElement[0] != $(ui.item).parent()[0]
+        )
 sendSortedNodeData = (ul) ->
   nodes = ul.children('li[data-type="Node"]')
   nodeData = []


namespaced unbind

namespaced unbind

--- test/bindable.coffee
+++ test/bindable.coffee
@@ -51,7 +51,8 @@

   o.trigger "test"

-  o.unbind ""
+  o.unbind "TestNamespace", ->
+

 module()


transaction mock

transaction mock

--- app/spec/utils/dongle/mock_dongle.coffee
+++ app/spec/utils/dongle/mock_dongle.coffee
@@ -181,33 +181,55 @@
           when 'string'
             result = result.toString(HEX)

-        ###
-        ###
+
+        # Mock
         txb = new bitcoin.TransactionBuilder()

-        inputs.forEach (input, index) ->
-          input.signs.forEach (sign) ->
-            keyPair = ECPair.fromWIF(sign.keyPair)
-            txb.sign(index, keyPair, redeemScript, sign.hashType)
-
-
-        # Add the input (who is paying) of the form [previous transaction hash, index of the output to use]
-        tx.addInput("aa94ab02c182214f090e99a0d57021caffd0f195a81c24602b1028b130b63e31", 0)
-
-        # Add the output (who to pay to) of the form [payee's address, amount in satoshis]
-        tx.addOutput(@bitcoinAddress, amount.toSatoshiNumber())
-
-        # Initialize a private key using WIF
-        key = bitcoin.ECKey.fromWIF("L1uyy5qTuGrVXrmrsvHWHgVzW9kKdrp27wBC7Vs6nZDTF2BRUVwy")
-
-        # Sign the first input with the new key
-        tx.sign(0, key)
-
-        # Print transaction serialized as hex
-        console.log(tx.toHex())
-        # => 0100000001313eb630b128102b60241ca895f1d0ffca21 ...
-        ###
-        ###
+        rawTxs = for input in @_btInputs
+          [splittedTx, outputIndex] = input
+          rawTxBuffer = splittedTx.version
+          rawTxBuffer = rawTxBuffer.concat(new ByteString(Convert.toHexByte(splittedTx.inputs.length), HEX))
+          for input in splittedTx.inputs
+            rawTxBuffer = rawTxBuffer.concat(input.prevout).concat(new ByteString(Convert.toHexByte(input.script.length), HEX)).concat(input.script).concat(input.sequence)
+          rawTxBuffer = rawTxBuffer.concat(new ByteString(Convert.toHexByte(splittedTx.outputs.length), HEX))
+          for output in splittedTx.outputs
+            rawTxBuffer = rawTxBuffer.concat(output.amount).concat(new ByteString(Convert.toHexByte(output.script.length), HEX)).concat(output.script)
+          rawTxBuffer = rawTxBuffer.concat(splittedTx.locktime)
+          [rawTxBuffer, outputIndex]
+
+        values = []
+        balance = Bitcoin.BigInteger.valueOf(0)
+
+        for [rawTx, outputIndex] in rawTxs
+          tx = bitcoin.Transaction.fromHex(rawTx.toString())
+          txb.addInput(tx, outputIndex)
+          values.push(tx.outs[outputIndex].value)
+        for val, i in values
+          balance = balance.add Bitcoin.BigInteger.valueOf(val)
+
+        change = (balance.toString() - @fees) - @amount.toString()
+
+        l balance.toString()
+        l @amount
+        l @fees
+
+        # Get Hash from base58
+        arr = ledger.crypto.Base58.decode(@recipientAddress)
+        buffer = JSUCrypt.utils.byteArrayToHexStr(arr)
+        x = new ByteString(buffer, HEX)
+        pubKeyHash = x.bytes(0, x.length - 4).bytes(1) # remove network 1, remove checksum 4
+        l pubKeyHash
+
+        scriptPubKeyStart = Convert.toHexByte(bitcoin.opcodes.OP_DUP) + Convert.toHexByte(bitcoin.opcodes.OP_HASH160) + 14
+        scriptPubKeyEnd = Convert.toHexByte(bitcoin.opcodes.OP_EQUALVERIFY) + Convert.toHexByte(bitcoin.opcodes.OP_CHECKSIG)
+        scriptPubKey = bitcoin.Script.fromHex(scriptPubKeyStart + pubKeyHash.toString() + scriptPubKeyEnd)
+
+        txb.addOutput(scriptPubKey, @amount.toNumber()) # recipient addr
+        txb.addOutput(scriptPubKey, change) # change addr
+
+
+        #for input, index in tx.ins
+        #txb.sign(index, tx.ins[index].script.toHex())

         return result


cooldown finilazation

cooldown finilazation

--- test/cooldown.coffee
+++ test/cooldown.coffee
@@ -106,6 +106,6 @@
   obj.include(Cooldown)

   obj.cooldown 'health',
-    target: 100
+    target: 10

 module()


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/distance.coffee
+++ test/distance.coffee
@@ -22,6 +22,6 @@
     width: 7
     height: 20

-  equals,
+  equals, player.distance(enemy), Point.distance()

 module()


Make pubkey_hash actually work

Make pubkey_hash actually work

--- server/models.coffee
+++ server/models.coffee
@@ -1,14 +1,13 @@
 { Schema } = require 'mongoose'
 timestamp = require 'mongoose-time'
+crypto = require 'crypto'
 require 'mongoose-pagination'

 to_buff = (val) -> if val instanceof Buffer then val else new Buffer val, 'base64'
 buff_getter = (key, encoding) -> -> this[key].toString encoding
-triple_sha256 = (bytes) ->
-  bytes
-  # TODO load sha256b and use that
-  # ensure that it works well on buffers, or cast it to an array
-  # sha256b sha256b sha256b bytes
+sha256 = (data) -> crypto.createHash('sha256').update(data).digest()
+
+triple_sha256 = (bytes) -> sha256 sha256 sha256 bytes

 TX_EXPIRY = '24h'


Renaming config.paths.clientRouter to config.paths.routes

--- server/router.coffee
+++ server/router.coffee
@@ -6,7 +6,7 @@
 #   - errorHandler: function to correctly handle error
 #   - paths
 #     - entryPath (required)
-#     - clientRouter (optional)
+#     - routes (optional)
 #     - controllerDir (optional)
 #   - stashPerf: optional function to store performance stats
 #   - stashError: optional function to notify server of error
@@ -15,8 +15,8 @@
   config = conf
   if (!config.paths || !config.paths.entryPath)
     return callback("Missing entryPath")
-  if (!config.paths.clientRouter)
-    config.paths.clientRouter = config.paths.entryPath + '/routes'
+  if (!config.paths.routes)
+    config.paths.routes = config.paths.entryPath + '/routes'
   if (!config.paths.controllerDir)
     config.paths.controllerDir = config.paths.entryPath + '/controllers'
   callback()
@@ -31,7 +31,7 @@
 stashError = (req, err) ->
   if (config && config.stashError)
     config.stashError(req, err)
-
+

 getController = (controllerName) ->
   require("#{config.paths.controllerDir}/#{controllerName}_controller")
@@ -89,7 +89,7 @@
   stashError(req, err)
   if (config && config.errorHandler)
     config.errorHandler(err, req, res)
-  else
+  else
     # default error handler
     if (config && config.dumpExceptions)
       text = "Error: #{err.message}\n"
@@ -102,9 +102,9 @@

 # define routes
 exports.routes = () ->
-  clientRoutes = require(config.paths.clientRouter)
+  routes = require(config.paths.routes)
   routeSpecs = []
-  for own path, routeInfo of clientRoutes
+  for own path, routeInfo of routes
     action = getAction(routeInfo)
     handler = getHandler(action, routeInfo)
     routeSpecs.push(['get', "/#{path}", routeInfo.role, handler])


Add tests emitting each event

Add tests emitting each event

--- test/esa-test.coffee
+++ test/esa-test.coffee
@@ -33,7 +33,12 @@
       nock.cleanAll()

     context 'someone requests stats', ->
+      emitted = false
+      emitted_stats = null
       beforeEach (done) ->
+        room.robot.on 'esa.hear.stats', (res, stats) ->
+          emitted = true
+          emitted_stats = stats
         nockScope
           .get("/v1/teams/#{process.env.HUBOT_ESA_TEAM}/stats")
           .query(access_token: process.env.HUBOT_ESA_ACCESS_TOKEN)
@@ -47,9 +52,18 @@
           ['hubot', "Members: 20\nPosts: 1959\nComments: 2695\nStars: 3115\nDaily Active Users: 8\nWeekly Active Users: 14\nMonthly Active Users: 15"]
         ]

+      it 'emits esa.hear.stats event with args', ->
+        expect(emitted).to.equal true
+        expect(emitted_stats.members).to.equal 20
+
     describe 'post', ->
       context 'in own team', ->
+        emitted = false
+        emitted_post = null
         beforeEach ->
+          room.robot.on 'esa.hear.post', (res, post) ->
+            emitted = true
+            emitted_post = post
           nockScope
             .get("/v1/teams/#{process.env.HUBOT_ESA_TEAM}/posts/1390")
             .query(access_token: process.env.HUBOT_ESA_ACCESS_TOKEN)
@@ -66,6 +80,10 @@
               ['hubot', 'esa: 日報/2015/05/09/hi! #api #dev']
             ]

+          it 'emits esa.hear.post event with args', ->
+            expect(emitted).to.equal true
+            expect(emitted_post.name).to.equal 'hi!'
+
         context 'someone says post url wtih anchor', ->
           beforeEach (done) ->
             room.user.say('gingy', 'https://ginger.esa.io/posts/1390#1-1-1')
@@ -89,7 +107,12 @@

     describe 'comment', ->
       context 'in own team', ->
-        beforeEach (done) ->
+        emitted = false
+        emitted_comment = null
+        beforeEach (done) ->
+          room.robot.on 'esa.hear.comment', (res, comment) ->
+            emitted = true
+            emitted_comment = comment
           nockScope
             .get("/v1/teams/#{process.env.HUBOT_ESA_TEAM}/comments/2121")
             .query(access_token: process.env.HUBOT_ESA_ACCESS_TOKEN)
@@ -102,6 +125,10 @@
             ['gingy', 'https://ginger.esa.io/posts/1390#comment-2121']
             ['hubot', 'esa: 読みたい']
           ]
+
+        it 'emits esa.hear.comment event with args', ->
+          expect(emitted).to.equal true
+          expect(emitted_comment.body_md).to.equal '読みたい'

       context 'in other team, someone says comment url', ->
         beforeEach (done) ->


OPCE4: Selection constructor accepts only absolute positions

--- test/rte-test.coffee
+++ test/rte-test.coffee
@@ -62,19 +62,19 @@

   it 'not delete space after word when selection ends at last character', ->
     rte1 = new Rte "I am yjs here!"
-    sel = new Selection({word:2, pos:0}, {word:2, pos:3})
+    sel = new Selection(5, 8, rte1)
     rte1.deleteSel(sel)
     rte1.val().should.equal "I am  here!"

   it 'delete space and merge (if necessary)', ->
     rte1 = new Rte "y jjs is here!"
-    sel = new Selection({word:0, pos:1}, {word:1, pos:1})
+    sel = new Selection(1, 3, rte1)
     rte1.deleteSel(sel)
     rte1.val().should.equal "yjs is here!"

   it 'merge words when no space anymore', ->
     rte1 = new Rte "yjs is is here!"
-    sel = new Selection({word:1, pos:0}, {word:2, pos:0})
+    sel = new Selection(4, 7, rte1)
     rte1.deleteSel(sel)
     rte1.val().should.equal "yjs is here!"

@@ -109,16 +109,15 @@
     rte1.delta delta
     rte1.val().should.equal "Gandalf Grey"

+  it 'should accept deltas (style)', ->
+    delta = {ops:[
+      { retain: 7, attributes: {bold: true } }]}
+    rte1 = new Rte "Gandalf the Grey"
+    rte1.delta delta
+    console.log rte1.getWord(0)
+
 describe 'Selection object should', ->
   sel = rte = null
-  it 'be initialized with two parameters', ->
-    [a, b, c, d] = [1, 2, 3, 4]
-    sel = new Selection({word: 1, pos: 2}, {word: 3, pos: 4})
-
-    sel.startPos.word.should.equal a
-    sel.startPos.pos.should.equal b
-    sel.endPos.word.should.equal c
-    sel.endPos.pos.should.equal d

   it 'be initialized with three parameters', ->
     rte = new Rte "Zero One two three four five"
@@ -131,14 +130,3 @@
     sel.should.have.property('endPos')
     sel.should.have.deep.property('endPos.word', 1)
     sel.should.have.deep.property('endPos.pos', 2)
-
-  it 'be initialized with four parameters', ->
-    sel = new Selection 0, 1, 2, 3
-
-    sel.should.have.property('startPos')
-    sel.should.have.deep.property('startPos.word', 0)
-    sel.should.have.deep.property('startPos.pos', 1)
-
-    sel.should.have.property('endPos')
-    sel.should.have.deep.property('endPos.word', 2)
-    sel.should.have.deep.property('endPos.pos', 3)


restore sshkey stubs

restore sshkey stubs

--- test/ssh_keys.coffee
+++ test/ssh_keys.coffee
@@ -59,6 +59,8 @@

     after ->
       fs.writeFileSync.restore()
+      fs.chmodSync.restore()
+      child_process.exec.restore()

     describe 'when the apikey is valid and box exists', ->
       froth = null


Reorg the debug line and improve the check

We should only access the instance variables once we know what kind of instance it is

#6 Classify and objectify query handlers

--- client.coffee
+++ client.coffee
@@ -14,6 +14,7 @@
 parallel = require('run-parallel')
 string2compact = require('string2compact')
 utils = require './utils'
+BaseQueryHandler = require "./src/queryhandlers/BaseQueryHandler"
 FindNodeQueryHandler = require "./src/queryhandlers/FindNodeQueryHandler"
 PingQueryHandler = require "./src/queryhandlers/PingQueryHandler"

@@ -501,8 +502,8 @@
 DHT::_onQuery = (addr, message) ->
   query = message.q.toString()
   handler = @queryHandler[query]
-  @_debug "handler:", handler.name
-  if typeof handler == "object"
+  if handler instanceof BaseQueryHandler
+    @_debug "handler:", handler.name
     return handler.handle message
   else
     throw new TypeError "unexpected query type"


Actually fix ranking numbers now

Actually fix ranking numbers now

--- client.coffee
+++ client.coffee
@@ -56,12 +56,13 @@
 				end = Db.shared.peek('game', 'endTime')
 				if(nEnd isnt 0)
 					end = nEnd
-				#make the subtitle red if the game is within 1 hour of ending
+				#make the subtitle black if the game is within 1 hour of ending
 				Page.setTitle !->
 					if (end - Plugin.time()) < 3600
 						Dom.div !->
 							Dom.style
-								color: "red"
+								color: "#000000"
+								fontWeight: 'bold'
 							Dom.text "Game ends "
 							Time.deltaText end
 					else
@@ -111,7 +112,7 @@
 			Icon.render data: 'info', color: '#fff', size: 30, style: {verticalAlign: 'middle'}
 			Dom.div !->
 				Dom.text 'Help'
-				Dom.style verticalAlign: 'middle', display: 'inline-block', marginLeft: '5px', fontSize: '15px'
+				Dom.style verticalAlign: 'middle', display: 'inline-block', marginLeft: '5px', fontSize: '13px'
 			Dom.cls 'bar-button'
 			Dom.onTap !->
 				Page.nav 'help'
@@ -120,7 +121,7 @@
 			Icon.render data: 'clipboard', color: '#fff', size: 30, style: {verticalAlign: 'middle'}
 			Dom.div !->
 				Dom.text 'Events'
-				Dom.style verticalAlign: 'middle', display: 'inline-block', marginLeft: '5px', fontSize: '15px'
+				Dom.style verticalAlign: 'middle', display: 'inline-block', marginLeft: '5px', fontSize: '13px'
 			Dom.cls 'bar-button'
 			Dom.onTap !->
 				Page.nav 'log'
@@ -129,7 +130,7 @@
 			Icon.render data: 'award4', color: '#fff', size: 30, style: {verticalAlign: 'middle'}
 			Dom.div !->
 				Dom.text 'Ranking'
-				Dom.style verticalAlign: 'middle', display: 'inline-block', marginLeft: '5px', fontSize: '15px'
+				Dom.style verticalAlign: 'middle', display: 'inline-block', marginLeft: '5px', fontSize: '13px'
 			Dom.cls 'bar-button'
 			Dom.onTap !->
 				Page.nav 'scores'
@@ -274,7 +275,7 @@
 		Dom.text "This way you can keep track of what is going on in the game and how certain teams or individuals are performing. "

 scoresContent = ->
-	position = 0
+	#position = 0
 	Dom.div !->
 		Dom.style
 			paddingLeft: "14px"
@@ -286,10 +287,15 @@
 			teamColor = Db.shared.peek('colors', team.key(), 'hex')
 			teamName = Db.shared.peek('colors', team.key(), 'name')
 			teamScore = Db.shared.get('game', 'teams', team.key(), 'teamScore')
-			position = position + 1
+			#position = position + 1
 			# list of teams and their scores
 			expanded = Obs.create(false)
 			Ui.item !->
+				position = 1;
+				item = Dom._get()
+				while((item = item.previousSibling) != null )
+					position++;
+
 				Dom.style
 					padding: '14px'
 					minHeight: '71px'


Add `close` method with optional callback argument

--- src/index.coffee
+++ src/index.coffee
@@ -138,6 +138,11 @@
     then callback()
     else @_server.once "listening", callback

+  close: (callback) ->
+    @_server.once "close", callback if callback
+    @_server.close()
+    return this
+
   # Used for testing.
   _send: (req, res) ->
     onRequest.call this, req, res


Parse the cassandra hosts

Parse the cassandra hosts

--- src/index.coffee
+++ src/index.coffee
@@ -227,7 +227,7 @@
     Q(schemaVersion)

-cassandrahosts = (val) ->
+parseCassandraHosts = (val) ->
   val.split(',')

 # Run the script
@@ -239,7 +239,7 @@
     .option '-d, --debug', 'Increase verbosity and error detail'
     .option '-k, --keyspace <keyspace>', 'Cassandra keyspace used for migration and schema_version table'
     .option '-t, --target-version <version>', 'Maximum migration version to apply (default runs all migrations)'
-    .option '-h, --hosts <hosts>', 'A comma separated list of cassandra hosts'
+    .option '-h, --hosts <hosts>', 'A comma separated list of cassandra hosts', parseCassandrahosts
     .parse(process.argv)

   configFile = _(program.args).last()


exposed classes

exposed classes

--- src/index.coffee
+++ src/index.coffee
@@ -1,5 +1,9 @@
-Wallet = require('./wallet')
-Custodian = require('./custodian')
+Custodian = require './custodian'
+Definition = require './definition'
+
+Wallet = require './wallet'
+Document = require './collection'
+Collection = require './collection'

 class YouBase
   constructor: (@custodian='api.youbase.io') ->
@@ -9,4 +13,11 @@

   wallet: -> new Wallet(@custodian, arguments...)

+YouBase.Custodian = Custodian
+YouBase.Definition = Definition
+
+YouBase.Wallet = Wallet
+YouBase.Document = Document
+YouBase.Collection = Collection
+
 exports = module.exports = YouBase


save referrer

save referrer

--- src/index.coffee
+++ src/index.coffee
@@ -90,7 +90,6 @@

 getReferrer = (qs)->
   if qs.referrer?
-    store.set 'referrer', qs.referrer
     return qs.referrer
   else
     return store.get 'referrer'
@@ -115,6 +114,8 @@
       referrer = r
   else
     referrer = getreferrer(queries) ? opts.order?.referrer
+
+  store.set 'referrer', referrer

   promo = queries.promo ? ''