setPosition, taken from https://atom.io/packages/autocomplete-plus

--- lib/atom-ternjs-view.coffee
+++ lib/atom-ternjs-view.coffee
@@ -60,17 +60,31 @@
     false

   getFilterKey: -> 'name'
-
+
+  # Taken from https://atom.io/packages/autocomplete-plus
   setPosition: ->
-    { left, top } = @editorView.pixelPositionForScreenPosition(@editor.getCursorScreenPosition())
-    height = @outerHeight()
-    potentialTop = top + @editorView.lineHeight
-    potentialBottom = potentialTop - @editorView.scrollTop() + height
-    if @aboveCursor or potentialBottom > @editorView.outerHeight()
-      @aboveCursor = true
-      @css(left: left, top: top - height, bottom: 'inherit')
+    { left, top } = @editorView.pixelPositionForScreenPosition @editor.getCursorScreenPosition()
+    cursorLeft = left
+    cursorTop = top
+
+    # The top position if we would put it below the current line
+    belowPosition = cursorTop + @editorView.lineHeight
+
+    # The top position of the lower edge if we would put it below the current line
+    belowLowerPosition = belowPosition + @outerHeight()
+
+    # The position if we would put it above the line
+    abovePosition = cursorTop
+
+    if belowLowerPosition > @editorView.outerHeight() + @editorView.scrollTop()
+      # We can't put it below - put it above. Using CSS transforms to
+      # move it 100% up so that the lower edge is above the current line
+      @css left: cursorLeft, top: abovePosition
+      @css '-webkit-transform', 'translateY(-100%)'
     else
-      @css(left: left, top: potentialTop, bottom: 'inherit')
+      # We can put it below, remove possible previous CSS transforms
+      @css left: cursorLeft, top: belowPosition
+      @css '-webkit-transform', ''

   afterAttach: (onDom) ->
     if onDom


Fat arrow not required.

Fat arrow not required.

--- lib/babel-transpiler.coffee
+++ lib/babel-transpiler.coffee
@@ -307,7 +307,7 @@
   # merge babelrc options as per babel code in
   # https://github.com/babel/babel/blob/master/src/babel/helpers/merge.js
   mergeBabelrc: (dest, src) ->
-    merge dest, src, (a,b) =>
+    merge dest, src, (a,b) ->
       if (Array.isArray(a))
         c = a.slice(0)
         for k, v of b


Cleanup and refactor

Cleanup and refactor

--- src/config/inspector.coffee
+++ src/config/inspector.coffee
@@ -18,7 +18,7 @@
     click: (world, args) -> world.send world.get('value')
   }

-list_program = (name, commands) ->
+display_program = (name, commands) ->
   children = commands.all()
   children.unshift {name: name, fill: "white", stroke: "white"}
   {
@@ -26,6 +26,12 @@
     _AUTHORITY: BUTTON_AUTHORITY
     _CHILDREN: children
   }
+
+display_strategy = (strategy, programs) ->
+  strategy.reset_children()
+  programs.map_children (child) ->
+    strategy.add_child display_program(child.label(), child.get('value'))
+  strategy.put 'height', my.row.spacing*programs._child_count()

 exports.inspector = {
   _LABEL: "inspector"
@@ -35,12 +41,9 @@
     my.assert world.is_world current, "can only inspect worlds"
     world.put('current', current)
     strategy = world.find_child('strategy')
+    strategy.authority = world.make_world ROW_AUTHORITY
     programs = current.get('programs')
-    strategy.authority = world.make_world ROW_AUTHORITY
-    strategy.reset_children()
-    programs.map_children (child) ->
-      strategy.add_child list_program(child.label(), child.get('value'))
-    strategy.put 'height', my.row.spacing*programs._child_count()
+    display_strategy strategy, programs

   i: (world) -> world.get('split')
   width: (world) -> world.get('device').width - world.get('size')
@@ -73,7 +76,7 @@
     }
     'commands'
     'program'
-    'strategy'
+    {_LABEL: 'strategy'}
   ]

 }


Fix expected quantity auto-incrementing on stock transfer edit

When a variant that already exists in the stock transfer is added again, it should increment the current expected value by 1. I wasn’t incrementing the parsed value so the expected quantity would never change.

--- backend/app/assets/javascripts/spree/backend/stock_transfers/variant_form.coffee
+++ backend/app/assets/javascripts/spree/backend/stock_transfers/variant_form.coffee
@@ -42,7 +42,7 @@
       transferItem = new Spree.TransferItem
         id: transferItemId
         stockTransferNumber: stockTransferNumber
-        expectedQuantity: expectedQuantity
+        expectedQuantity: expectedQuantity + 1
       transferItem.update(updateSuccessHandler, errorHandler)
     else
       transferItem = new Spree.TransferItem


Timing tweaks for IE

Timing tweaks for IE

--- spec/Initialization.coffee
+++ spec/Initialization.coffee
@@ -11,7 +11,7 @@
       doc = iframe.contentDocument
       setTimeout ->
         done()
-      , 1000
+      , 7000
   after ->
     db.close()

@@ -23,6 +23,8 @@
     req.onerror = ->
       chai.expect(true).to.equal false
       done()
+    req.onupgradeneeded = (e) =>
+      throw new Error 'We didn\'t get a ready database'
     req.onsuccess = (event) ->
       db = event.target.result
       chai.expect(db).to.be.an 'object'


Only animations with x/y props are stopped

--- framer/LayerDraggable.coffee
+++ framer/LayerDraggable.coffee
@@ -133,8 +133,8 @@
 		@_isMoving = @isAnimating

 		for animation in @layer.animations()
-			if animation.options.properties.x or animation.options.properties.y
-				@layer.animateStop()
+			if animation.options.properties.hasOwnProperty('x') or animation.options.properties.hasOwnProperty('y')
+				animation.stop()

 		@_stopSimulation()
 		@_resetdirectionLock()


remove my debugging output

remove my debugging output

--- gameState.coffee
+++ gameState.coffee
@@ -571,7 +571,7 @@
     # information is actually used by Smugglers.)
     @current.gainedThisTurn = []

-    if this.depth == 0
+    if this.depth == 0 and this.debug?
       estimatedBuys = @current.ai.pessimisticCardsGained(this)
       this.log("#{@current.ai} plans to buy #{estimatedBuys}.")


Spec for the info message when there are no results.

--- spec/find-view-spec.coffee
+++ spec/find-view-spec.coffee
@@ -96,6 +96,21 @@
       expect(shell.beep).toHaveBeenCalled()
       expect(findView.find(':focus')).toExist()

+      expect(findView.infoMessages.children()).toHaveLength 1
+
+    it "properly handles the info message when there are no results", ->
+      findView.findEditor.setText 'item'
+      findView.findEditor.trigger 'core:confirm'
+      expect(findView.infoMessages.children()).toHaveLength 0
+
+      findView.findEditor.setText 'notinthefilenope'
+      findView.findEditor.trigger 'core:confirm'
+      expect(findView.infoMessages.children()).toHaveLength 1
+
+      findView.findEditor.setText 'item'
+      findView.findEditor.trigger 'core:confirm'
+      expect(findView.infoMessages.children()).toHaveLength 0
+
     it "selects the first match following the cursor", ->
       expect(findView.resultCounter.text()).toEqual('2 of 6')
       expect(editor.getSelectedBufferRange()).toEqual [[2, 8], [2, 13]]


Build in spec folder

Build in spec folder

--- spec/installer-spec.coffee
+++ spec/installer-spec.coffee
@@ -6,7 +6,7 @@

 describe 'create-windows-installer task', ->
   it 'creates a nuget package', ->
-    outputDirectory = temp.mkdirSync('grunt-atom-shell-installer-')
+    outputDirectory = __drname

     grunt.config.init
       pkg: grunt.file.readJSON(path.join(__dirname, 'fixtures', 'app', 'resources', 'app', 'package.json'))


Fix tests on OSX

Fix tests on OSX

Not sure why paths must be real paths here but apparently they do.

--- spec/tree-view-spec.coffee
+++ spec/tree-view-spec.coffee
@@ -683,7 +683,7 @@
     beforeEach ->
       atom.deactivatePackage('tree-view')

-      rootDirPath = path.join(os.tmpdir(), "atom-tests")
+      rootDirPath = path.join(fs.absolute(os.tmpdir()), 'atom-tests')

       dirPath = path.join(rootDirPath, "test-dir")
       filePath = path.join(dirPath, "test-file.txt")


Fix spec

Fix spec

--- spec/tree-view-spec.coffee
+++ spec/tree-view-spec.coffee
@@ -1617,7 +1617,7 @@
         runs ->
           treeView.trigger 'tree-view:remove'
           args = atom.confirm.mostRecentCall.args[0]
-          expect(Object.keys(args.buttons)).toEqual ['Move to Trash', 'Cancel', 'Delete']
+          expect(Object.keys(args.buttons)).toEqual ['Move to Trash', 'Delete', 'Cancel']

   describe "file system events", ->
     temporaryFilePath = null


Fix last failing tests on Windows

Fix last failing tests on Windows

--- spec/tree-view-spec.coffee
+++ spec/tree-view-spec.coffee
@@ -2739,8 +2739,12 @@
           fs.writeFileSync(modifiedFile, originalFileContent)
           atom.project.getRepositories()[0].getPathStatus(modifiedFile)

-          expect(treeView.element.querySelector('.file.status-modified')).not.toExist()
-          expect(treeView.element.querySelector('.directory.status-modified')).not.toExist()
+          waitsFor ->
+            not treeView.element.querySelector('.file.status-modified')
+
+          runs ->
+            expect(treeView.element.querySelector('.file.status-modified')).not.toExist()
+            expect(treeView.element.querySelector('.directory.status-modified')).not.toExist()

   describe "when the resize handle is double clicked", ->
     beforeEach ->
@@ -3414,7 +3418,7 @@
             expect(treeView).toHaveFocus()

           it "doesn't open the file in the active pane", ->
-            expect(atom.views.getView(treeView).element).toHaveFocus()
+            expect(atom.views.getView(treeView)).toHaveFocus()
             expect(activePaneItem.getPath()).toBe atom.project.getDirectories()[0].resolve('tree-view.js')

       describe "when a file is double-clicked", ->


Refactored Jplayer and created a React component for TextBox

--- app/assets/javascripts/rglossa/speech/components/centre/results/jplayer.jsx.coffee
+++ app/assets/javascripts/rglossa/speech/components/centre/results/jplayer.jsx.coffee
@@ -1,11 +1,63 @@
 ###* @jsx React.DOM ###
+
+window.TextBox = React.createClass
+  propTypes:
+    mediaObj: React.PropTypes.object.isRequired
+    startAtLine: React.PropTypes.number.isRequired
+    endAtLine: React.PropTypes.number.isRequired
+
+  renderWord: (line) ->
+    att_string = ""
+    for att of line
+      if att is "pos"
+        # pos tags contain multiple values seperated by colons
+        line[att] = line[att].replace(/:/g,"/")
+      att_string += att + " : " + line[att] + "
"
+    `<a title={att_string} style={line.match ? {color: '#b00', fontWeight: 'bold', fontSize: '0.9em'} : {}}>{line[this.props.mediaObj.display_attribute]} </a>`
+
+  renderAnnotation: (annotation, lineNo) ->
+      timecode = annotation.from
+      end_timecode = annotation.to
+      speaker = annotation.speaker
+      speaker_brev = speaker.replace(/^.*_/,"")
+      segment = (@renderWord(annotation.line[i]) for i of annotation.line)
+      getStyle = () =>
+        if parseInt(lineNo) == parseInt(@props.highlightLine)
+          { display: 'table-row', color: '#000', 'background-color': '#eea' }
+        else if lineNo >= @props.startAtLine and lineNo <= @props.endAtLine
+          { display: 'table-row', color: '#000' }
+        else if annotation.from is @first_start or annotation.to is @last_end
+          # ie, overlapping segments
+          { display: 'table-row', color: '#ccc' }
+        else
+          { display: 'none' }
+
+      `

+
{speaker_brev}

+
{segment}

+
`
+
+  render: ->
+    display_attribute = @props.mediaObj.display_attribute
+    annotation = @props.mediaObj.divs.annotation
+    @first_start = annotation[@props.startAtLine].from
+    @last_end = annotation[@props.endAtLine].to
+
+    annotations = (@renderAnnotation(annotation[n], n) for n of annotation)
+    `<div>{annotations}</div>`
+

 window.Jplayer = React.createClass
   propTypes:
     mediaObj: React.PropTypes.object.isRequired
     mediaType: React.PropTypes.string.isRequired

-  getStartTime: (mediaObj) ->
+  getStartLine: (mediaObj) ->
     start_at = parseInt(mediaObj.start_at)
     min_start = parseInt(mediaObj.min_start)
     if !@props.ctx_lines
@@ -15,7 +67,7 @@
     else
       min_start

-  getEndTime: (mediaObj) ->
+  getEndLine: (mediaObj) ->
     end_at = parseInt(mediaObj.end_at)
     max_end = parseInt(mediaObj.max_end)
     if !@props.ctx_lines
@@ -25,12 +77,23 @@
     else
       max_end

+  getStartTime: (mediaObj) ->
+    parseFloat mediaObj.divs.annotation[@state.startLine].from
+
+  getEndTime: (mediaObj) ->
+    parseFloat mediaObj.divs.annotation[@state.endLine].to
+
+  getInitialState: ->
+    startLine: @getStartLine(@props.mediaObj)
+    endLine: @getEndLine(@props.mediaObj)
+    currentLine: @getStartLine(@props.mediaObj)
+    restart: false
+
   componentDidMount: ->
     @createPlayer()

   componentDidUpdate: ->
-    @destroyPlayer()
-    @createPlayer()
+    @restartPlayer() if @state.restart

   componentWillUnmount: ->
     @destroyPlayer()
@@ -38,7 +101,6 @@
   createPlayer: ->
     $node = $(@getDOMNode())
     mediaObj = @props.mediaObj
-    @textBox = @createTextBox()

     $(document).tooltip
       content: -> $node.prop('title')
@@ -49,38 +111,26 @@
     $("#movietitle").text(mediaObj.title)
     last_line = parseInt(mediaObj.last_line)

-    @textBox.init($node, mediaObj, @getStartTime(mediaObj), @getEndTime(mediaObj))
-
-    @textBox.currentID = @getStartTime(mediaObj)
-
-    start = @getStartTime(mediaObj)
-    stop  = @getEndTime(mediaObj)
-    @textBox.redraw(start,stop, last_line)
-    start = parseFloat($("#jp-"+start).data("start_timecode"))
-    stop  = parseFloat($("#jp-"+stop).data("end_timecode"))
-
-    console.log(mediaObj)
-
     $playerNode = $node.find(".jp-jplayer")
     $playerNode.jPlayer
       solution: "flash, html"
-      ready: ->
+      ready: =>
         $playerNode.jPlayer "setMedia",
           rtmpv: path + mov
           m4v: path+mov
           poster: "assets/rglossa/speech/_6.6-%27T%27_ligo.skev.graa.jpg"
-        $playerNode.jPlayer( "play", start)
+        $playerNode.jPlayer( "play", @getStartTime(mediaObj))

       timeupdate: (event) =>
         ct = event.jPlayer.status.currentTime
-        if ct > stop
+        if ct > @getEndTime(mediaObj)
           $playerNode = $node.find(".jp-jplayer")
-          $playerNode.jPlayer("play", start)
+          $playerNode.jPlayer("play", @getStartTime(mediaObj))
           $playerNode.jPlayer( "pause" )
-        else if ct > @textBox.currentEndTime
-          @textBox.update(ct)
-
-      ended: -> alert("ended!")
+        else if ct > mediaObj.divs.annotation[@state.currentLine].to
+          @setState(currentLine: @state.currentLine+1, restart: false)
+
+#      ended: -> alert("ended!")

       swfPath: ""
       supplied: supplied
@@ -93,147 +143,22 @@
         range: true
         min: 0
         max: last_line
-        values: [ @textBox.start_at_line, @textBox.end_at_line+1 ]
+        values: [ @state.startLine, @state.endLine+1 ]

         slide: ( event, ui ) =>
           return false if ui.values[1] - ui.values[0] < 1
-
-          first = ui.values[ 0 ]
-          last = ui.values[ 1 ] - 1  #eg,  2 - 3 means play 1 segment
-          @textBox.redraw(first,last, last_line)
-
-          start = parseFloat($("#jp-"+first).data("start_timecode"))
-          stop = parseFloat($("#jp-"+last).data("end_timecode"))
-
-          $node.find(".js-jplayer").jPlayer("play", start)
+          $playerNode.jPlayer( "stop" )
+          @setState(restart: true, currentLine: ui.values[0], startLine: ui.values[0], endLine: ui.values[1]-1) #eg,  2 - 3 means play 1 segment

   destroyPlayer: ->
     $node = $(@getDOMNode())
-    @textBox.empty($node)
     $node.find(".jp-jplayer").jPlayer('destroy')

-
-  createTextBox: =>
-    start_at_line:0
-    end_at_line:0
-    currentID:0
-    nextID:0
-    currentEndTime:0
-    currentStartTime:0
-
-    init: ($view, mediaObj, start_at_line, end_at_line) ->
-      display_attribute = mediaObj.display_attribute
-      annotation = mediaObj.divs.annotation
-      @start_at_line = start_at_line
-      @end_at_line = end_at_line
-      @currentID = @start_at_line
-      @nextID = @start_at_line + 1
-
-      for n of annotation
-        timecode = annotation[n].from
-        end_timecode = annotation[n].to
-        speaker = annotation[n].speaker
-        speaker_brev = speaker.replace(/^.*_/,"")
-
-        div = $('
')
-        .addClass("textDiv")
-        .addClass(timecode.replace(/\./,"_"))
-        .attr("id", 'jp-' + n)
-        .data("start_timecode",timecode)
-        .data("end_timecode",end_timecode)
-        .on "click", (e) -> alert($view.data("start_timecode")+" id:"+$view.attr("id"))
-
-        if n < @start_at_line or n > @end_at_line
-          div.css({"display":"none"})
-
-        speakerDiv = $('
')
-        .addClass('speakerDiv')
-
-        anchor = $('')
-        .addClass('speaker')
-        .text(speaker_brev)
-        .attr("title",speaker)
-
-        speakerDiv.append(anchor)
-
-        segmentDiv = $("
")
-        .addClass('segmentDiv')
-
-        line = annotation[n].line
-
-        for i of line
-          match = false;
-          att_string = ""
-
-          match = true if line[i].match
-
-          for att of line[i]
-            if att is "pos"
-              # pos tags contain multiple values seperated by colons
-              line[i][att] = line[i][att].replace(/:/g,"/")
-            att_string += att + " : " + line[i][att] + "
"
-
-          anchor = $('')
-          .attr("title",att_string)
-          .text(line[i][display_attribute])
-
-          segmentDiv.append(anchor)
-          segmentDiv.append(" ")
-
-          if match
-            anchor
-            .css({"color":"#b00"})
-            .css({"font-weight":"bold"})
-            .css({"font-size":"0.9em"})
-
-        div.append(speakerDiv)
-        div.append(segmentDiv)
-        $view.find('.jplayer-text').append(div)
-
-
-    redraw: (first, last, last_line) ->
-      @currentID = first
-      @currentEndTime = 0
-      first_start = $("#jp-"+first).data("start_timecode")
-      last_end = $("#jp-"+last).data("end_timecode")
-
-      for i in [0..last_line]
-        $("#jp-"+i).css("background-color","#fff")
-        if i >= first and i <= last
-          $("#jp-"+i).css({"display":"table-row","color":"#000"})
-          continue
-
-        if ($("#jp-"+i).data("start_timecode") is first_start) or ($("#jp-"+i).data("end_timecode") is last_end)
-          # ie, overlapping segments
-          $("#jp-"+i).css({"display":"table-row","color":"#ccc"})
-          continue
-
-      $("#jp-"+i).css({"display":"none"})
-
-
-    update: (ct) ->
-      iterate = true
-
-      while iterate
-        currentEndTime = $("#jp-"+@currentID).data("end_timecode")
-        currentStartTime = $("#jp-"+@currentID).data("start_timecode")
-        if currentEndTime > ct
-          @currentEndTime = currentEndTime
-          $("."+currentStartTime.replace(/\./,"_")).css("background-color","#eea")
-          @currentEndTime = currentEndTime
-          iterate = false
-        else
-          $("."+currentStartTime.replace(/\./,"_")).css("background-color","#fff")
-          @currentID++
-
-      @nextID++
-      @currentEndTime
-
-
-    empty: ($view) ->
-      $view.find('.jplayer-text').empty()
-
+  restartPlayer: ->
+    $node = $(@getDOMNode())
+    $playerNode = $node.find(".jp-jplayer")
+    $playerNode.jPlayer("play", @getStartTime(@props.mediaObj))

   render: ->
     `

@@ -274,7 +199,7 @@

-

+
<TextBox mediaObj={this.props.mediaObj} startAtLine={this.state.startLine} endAtLine={this.state.endLine} highlightLine={this.state.currentLine} />


commentlistitem: use animatedRemoveMixin

--- client/activity/lib/views/comments/commentlistitemview.coffee
+++ client/activity/lib/views/comments/commentlistitemview.coffee
@@ -20,6 +20,7 @@
 isMyPost               = require 'app/util/isMyPost'
 hasPermission          = require 'app/util/hasPermission'
 updateEmbedBox         = require 'activity/mixins/updateembedbox'
+animatedRemoveMixin    = require 'activity/mixins/animatedremove'

 module.exports = class CommentListItemView extends KDListItemView

@@ -127,10 +128,10 @@
     modal.once 'DeleteError'     , @bound 'show'

-  hide          : (rest...) -> require('../activitylistitemview')::hide.apply this, rest
-  show          : (rest...) -> require('../activitylistitemview')::show.apply this, rest
-  delete        : (rest...) -> require('../activitylistitemview')::delete.apply this, rest
-  whenSubmitted : (rest...) -> require('../activitylistitemview')::whenSubmitted.apply this, rest
+  hide                 : animatedRemoveMixin.hide
+  show                 : animatedRemoveMixin.show
+  delete               : animatedRemoveMixin.remove
+  whenRemovingFinished : animatedRemoveMixin.whenRemovingFinished

   createReplyLink: ->


Test all the popover fixes

Test all the popover fixes

--- source/coffee/ntrapy.coffee
+++ source/coffee/ntrapy.coffee
@@ -245,7 +245,7 @@
         ntrapy.killPopovers() # We're moving so kill popovers
         keyed[nid].hovered = false # And cancel hovers
         if node.task_id?
-          console.log "Pending: #{node.task_id}: #{keyed[nid].facts.parent_id} -> #{node.facts.parent_id}"
+          #console.log "Pending: #{node.task_id}: #{keyed[nid].facts.parent_id} -> #{node.facts.parent_id}"
           node.facts.parent_id = keyed[nid].facts.parent_id # Ignore parent changes until tasks complete
         else
           console.log "Deleting: #{node.task_id}: #{keyed[nid].facts.parent_id} -> #{node.facts.parent_id}"
@@ -267,7 +267,7 @@
     node = keyed[id]
     pid = node.facts?.parent_id
     if pid? # Has parent ID?
-      console.log "Node: #{id}, Parent: #{pid}"
+      #console.log "Node: #{id}, Parent: #{pid}"
       pnode = keyed?[pid]
       if pnode? # Parent exists?
         pnode.children[id] = node # Add to parent's children
@@ -379,9 +379,9 @@
     opts["title"] =
       #TODO: Figure out why this fires twice: console.log "title"
       """
-      #{obj.name ? "Details"}
-

-        #{('

' + backend + '

' for backend in obj.facts.backends).join('')}
+      #{obj.name? ? "Details"}
+

+          #{('

' + backend + '

' for backend in obj.facts.backends).join('')}

       """
     opts["content"] =


use get-label

use get-label

--- src/test/test_render.coffee
+++ src/test/test_render.coffee
@@ -21,6 +21,8 @@
     contents = god(rx_mock(rx), dict)
     render(contents)

+  get_label = (tag) -> tag.attr.class[0]
+
   test "rx_mock exists", (t) ->
     t.ok world.T, 'Has HTML Tags'
     t.ok world.T().div, 'Has HTML div'
@@ -49,25 +51,19 @@
     t.ok rows, 'group rows'
     t.ok row_dict = rows('rows', ['alpha', 'beta']), 'create rows'
     t.ok row_tags = render_mock(row_dict).body.body, 'render rows'
+
+    t.ok tags = render_mock(row_dict).body, 'render rows'
+    t.ok row_tags = tags.body, 'extract rows'
     t.ok row = row_tags[0], "first row"
     t.equal row.tag, 'div', 'row tag'
-    t.equal row.attr.class[0], 'alpha', 'row label'
+    t.equal get_label(row), 'alpha', 'row label'
     t.end()

   test "render header", (t) ->
     t.ok header, 'header'
     body = render_mock(header).body
     t.equal body.tag, 'div', 'body.tag'
-    t.equal body.attr.class[0], header._LABEL, 'header label'
-
-    t.ok body.body, "header rows"
-    t.ok row = body.body[0], "first row"
-    t.equal row.tag, 'div', 'row tag'
-    t.equal row.attr.class[0], 'game', 'row label'
-
-    t.ok cols = body.body[3], 'last row'
-    t.ok col = cols.body[0], 'first col'
-    t.skip col.tag, 'span', 'col tag'
+    t.equal get_label(body), header._LABEL, 'header label'
     t.end()

   test "render children", (t) ->


Updates to make nano templates more awesome.

--- community/server/src/main/coffeescript/ribcage/ui/Nano.coffee
+++ community/server/src/main/coffeescript/ribcage/ui/Nano.coffee
@@ -14,8 +14,8 @@
       template.replace /\{([\w\-\,\.\|:]*)}/g, (str, key) ->
         pipes = key.split('|')
         keySets = for path in pipes.shift().split(",")
-          path.split(".")
-        console.log keySets[0]
+          path.split(".")
+
         for keys in keySets
           value = data[keys.shift()]
           for key in keys


JReferralCampaign: code cleanup, tidy up

--- workers/social/lib/social/models/referral/campaign.coffee
+++ workers/social/lib/social/models/referral/campaign.coffee
@@ -93,11 +93,27 @@

       @remove callback

-  REGISTER_CAMPAIGN = "register"

-  isCampaignValid = (campaignName, callback)->
-    [campaignName, callback] = [REGISTER_CAMPAIGN, campaignName] unless callback
+  DEFAULT_CAMPAIGN = "register"
+
+
+  @fetchCampaign = fetchCampaign = (campaignName, callback)->
+
+    unless callback
+      [campaignName, callback] = [DEFAULT_CAMPAIGN, campaignName]
+
+    JReferralCampaign.one name: campaignName, (err, campaign) ->
+      return callback err  if err
+      callback null, campaign
+
+
+  @isCampaignValid = isCampaignValid = (campaignName, callback)->
+
+    unless callback
+      [campaignName, callback] = [DEFAULT_CAMPAIGN, campaignName]
+
     fetchCampaign campaignName, (err, campaign)->
+
       return callback err  if err
       return callback null, isValid: no  unless campaign

@@ -106,12 +122,12 @@
         endDate, startDate } = campaign

       if Date.now() < startDate.getTime()
-        console.info "campaign is not started yet"
+        console.info "campaign #{campaignName} is not started yet"
         return callback null, isValid: no

       # if date is valid
       if Date.now() > endDate.getTime()
-        console.info "date is not valid for campaign"
+        console.info "date is not valid for campaign #{campaignName}"
         return callback null, isValid: no

       # if campaign initial amount is 0
@@ -125,21 +141,12 @@

       return callback null, { isValid: yes, campaign }

-  @isCampaignValid = isCampaignValid

-  @fetchCampaignDiskSize = (callback)->
-    @isCampaignValid (err, { isValid, campaign })->
-      return callback err if err
-      return callback null, campaign?.campaignPerEventAmount or 256
+  increaseGivenAmountSpace:(size, callback)->

-  @fetchCampaign = fetchCampaign = (campaignName, callback)->
-    [campaignName, callback] = [REGISTER_CAMPAIGN, campaignName] unless callback
-    JReferralCampaign.one {name: campaignName}, (err, campaign) ->
-      return callback err if err
-      return callback null, no  unless campaign
-      return callback null, campaign
+    unless callback
+      [size, callback] = [@campaignPerEventAmount, size]

-   increaseGivenAmountSpace:(size, callback)->
-    [size, callback] = [@campaignPerEventAmount, size] unless callback
     size = size * 4
-    @update $inc : campaignGivenAmount: size , callback
+
+    @update $inc: campaignGivenAmount: size , callback


emailchecker: added a basic protection if email is not passed

--- workers/social/lib/social/models/user/emailchecker.coffee
+++ workers/social/lib/social/models/user/emailchecker.coffee
@@ -224,6 +224,10 @@

 module.exports = (email, callback = ->)->

+  unless email
+    callback no
+    return no
+
   syncWithRedis -> callback check email

   return check email


fix meta tests

fix meta tests

--- apps/artist/test/meta.coffee
+++ apps/artist/test/meta.coffee
@@ -6,7 +6,7 @@

 describe 'Meta tags', ->
   before ->
-    @file = "#{process.cwd()}/apps/artist_old/templates/meta.jade"
+    @file = "#{process.cwd()}/apps/artist/templates/meta.jade"
     @sd =
       APP_URL: 'http://localhost:5000'
       CANONICAL_MOBILE_URL: 'http://m.localhost:5000'


[fail] added failing gutter spec

[fail] added failing gutter spec

When you remove a newline with backspace, the background on the gutter line number isn't highlighted. This is to help @kevinsawicki debug the problem.

--- spec/app/editor-spec.coffee
+++ spec/app/editor-spec.coffee
@@ -1639,6 +1639,14 @@
         expect(editor.find('.line-number.cursor-line-number').length).toBe 1
         expect(editor.find('.line-number.cursor-line-number.cursor-line-number-background').length).toBe 0
         expect(editor.find('.line-number.cursor-line-number').text()).toBe "3"
+
+    fit "when a newline is deleted with backspace, the line number of the new cursor position is highlighted (regression)", ->
+      editor.setCursorScreenPosition([1,0])
+      editor.backspace()
+      expect(editor.find('.line-number.cursor-line-number').length).toBe 1
+      expect(editor.find('.line-number.cursor-line-number').text()).toBe "1"
+      expect(editor.find('.line-number.cursor-line-number-background').length).toBe 1
+      expect(editor.find('.line-number.cursor-line-number-background').text()).toBe "1"

   describe "line highlighting", ->
     beforeEach ->


:green_heart: Fix uncertainty in tests

:green_heart: Fix uncertainty in tests

--- spec/csv-editor-spec.coffee
+++ spec/csv-editor-spec.coffee
@@ -9,73 +9,75 @@

 {click} = require './helpers/events'

-WRITE_TIMEOUT = 400
+CHANGE_TIMEOUT = 400

 describe "CSVEditor", ->
-  [csvEditor, csvEditorElement] = []
+  [csvEditor, csvEditorElement, csvDest, projectPath, tableEditor, tableEditorElement, openSpy, destroySpy, savedContent, spy] = []

   beforeEach ->
+    [csvEditor, csvEditorElement, csvDest, projectPath, tableEditor, tableEditorElement, openSpy, destroySpy, savedContent, spy] = []
+
     jasmineContent = document.body.querySelector('#jasmine-content')
     workspaceElement = atom.views.getView(atom.workspace)
     jasmineContent.appendChild(workspaceElement)

     waitsForPromise -> atom.packages.activatePackage('table-edit')

+  sleep = (ms) ->
+    start = new Date
+    -> new Date - start >= ms
+
+  openFixture = (fixtureName) ->
+    csvFixture = path.join(__dirname, 'fixtures', fixtureName)
+
+    projectPath = temp.mkdirSync('table-edit-project')
+    csvDest = path.join(projectPath, fixtureName)
+    fs.writeFileSync(csvDest, fs.readFileSync(csvFixture).toString().replace(/\s+$/g,''))
+
+    atom.project.setPaths([projectPath])
+
+    waitsForPromise ->
+      atom.workspace.open(fixtureName).then (t) ->
+        csvEditor = t
+        csvEditorElement = atom.views.getView(csvEditor)
+
+  modifyAndSave = (block) ->
+    waitsFor ->
+      csvEditorElement.querySelector('atom-table-editor')
+
+    runs block
+
+    runs ->
+      expect(tableEditor.isModified()).toBeTruthy()
+      expect(csvEditor.isModified()).toBeTruthy()
+
+      spyOn(fs, 'writeFile').andCallFake (path, data, callback) ->
+        savedContent = data
+        callback()
+
+      tableEditor.save()
+
+    waitsFor -> fs.writeFile.callCount > 0
+
+  afterEach ->
+    temp.cleanup()
+
   describe 'when a csv file is opened', ->
-    [csvDest, projectPath, tableEditor, tableEditorElement, openSpy, destroySpy, savedContent, spy] = []
-
-    sleep = (ms) ->
-      start = new Date
-      -> new Date - start >= ms
-
-    openFixture = (fixtureName) ->
-      csvFixture = path.join(__dirname, 'fixtures', fixtureName)
-
-      projectPath = temp.mkdirSync('table-edit-project')
-      csvDest = path.join(projectPath, fixtureName)
-      fs.writeFileSync(csvDest, fs.readFileSync(csvFixture).toString().replace(/\s+$/g,''))
-
-      atom.project.setPaths([projectPath])
-
-      waitsForPromise ->
-        atom.workspace.open(fixtureName).then (t) ->
-          csvEditor = t
-          csvEditorElement = atom.views.getView(csvEditor)
-
-    modifyAndSave = (block) ->
-      waitsFor ->
-        csvEditorElement.querySelector('atom-table-editor')
-
-      runs block
-
+    it 'opens a csv editor for the file', ->
+      openFixture('sample.csv')
       runs ->
-        expect(tableEditor.isModified()).toBeTruthy()
-        expect(csvEditor.isModified()).toBeTruthy()
-
-        spyOn(fs, 'writeFile').andCallFake (path, data, callback) ->
-          savedContent = data
-          callback()
-
-        tableEditor.save()
-
-      waitsFor -> fs.writeFile.callCount > 0
-
-    beforeEach ->
-      openFixture('sample.csv')
-
-    afterEach ->
-      temp.cleanup()
-
-    it 'opens a csv editor for the file', ->
-      expect(csvEditor instanceof CSVEditor).toBeTruthy()
+        expect(csvEditor instanceof CSVEditor).toBeTruthy()

     describe 'when the user choose to open a text editor', ->
       beforeEach ->
-        destroySpy = jasmine.createSpy('did-destroy')
-        csvEditor.onDidDestroy(destroySpy)
-
-        textEditorButton = csvEditorElement.openTextEditorButton
-        click(textEditorButton)
+        openFixture('sample.csv')
+
+        runs ->
+          destroySpy = jasmine.createSpy('did-destroy')
+          csvEditor.onDidDestroy(destroySpy)
+
+          textEditorButton = csvEditorElement.openTextEditorButton
+          click(textEditorButton)

         waitsFor ->
           destroySpy.callCount > 0
@@ -89,42 +91,46 @@
       it 'destroys the csv editor pane item', ->
         expect(atom.workspace.getActivePane().getItems().length).toEqual(1)

-      describe 'when the remember choice setting is enabled', ->
-        beforeEach ->
+    describe 'when the remember choice setting is enabled', ->
+      beforeEach ->
+        openFixture('sample.csv')
+
+        runs ->
+          csvEditorElement.querySelector('#remember-choice').checked = true
+
+          destroySpy = jasmine.createSpy('did-destroy')
+          csvEditor.onDidDestroy(destroySpy)
+
+        waitsFor sleep CHANGE_TIMEOUT
+
+        runs ->
+          textEditorButton = csvEditorElement.openTextEditorButton
+          click(textEditorButton)
+
+        waitsFor -> destroySpy.callCount > 0
+
+        runs ->
           csvEditor.destroy()

-          openFixture('sample.csv')
-
-          runs ->
-            csvEditorElement.querySelector('#remember-choice').checked = true
-
-            destroySpy = jasmine.createSpy('did-destroy')
-            csvEditor.onDidDestroy(destroySpy)
-
-            textEditorButton = csvEditorElement.openTextEditorButton
-            click(textEditorButton)
-
-          waitsFor -> destroySpy.callCount > 0
-
-          runs ->
-            csvEditor.destroy()
-
-          waitsForPromise ->
-            atom.workspace.open(path.join(projectPath, 'sample.json')).then (t) ->
-              csvEditor = t
-              csvEditorElement = atom.views.getView(csvEditor)
-
-        it 'does not show the choice form again', ->
-          expect(atom.workspace.getActiveTextEditor()).toBeDefined()
+        waitsForPromise ->
+          atom.workspace.open(path.join(projectPath, 'sample.json')).then (t) ->
+            csvEditor = t
+            csvEditorElement = atom.views.getView(csvEditor)
+
+      it 'does not show the choice form again', ->
+        expect(atom.workspace.getActiveTextEditor()).toBeDefined()

     describe 'when the user choose to open a table editor', ->
       describe 'with the default options', ->
         beforeEach ->
-          csvEditor.onDidOpen ({editor}) ->
-            tableEditor = editor
-
-          tableEditorButton = csvEditorElement.openTableEditorButton
-          click(tableEditorButton)
+          openFixture('sample.csv')
+
+          runs ->
+            csvEditor.onDidOpen ({editor}) ->
+              tableEditor = editor
+
+            tableEditorButton = csvEditorElement.openTableEditorButton
+            click(tableEditorButton)

           waitsFor -> tableEditor?

@@ -166,134 +172,139 @@
               expect(tableEditor.isModified()).toBeFalsy()
               expect(csvEditor.isModified()).toBeFalsy()

-        describe 'when the file cannot be parsed with the default', ->
-          beforeEach ->
-            tableEditor = tableEditorElement = null
-            openFixture('invalid.csv')
-
-            runs ->
-              tableEditorButton = csvEditorElement.openTableEditorButton
-              click(tableEditorButton)
-
-            waitsFor -> csvEditorElement.querySelector('.alert')
-
-          it 'displays the error in the settings form', ->
-            expect(csvEditorElement.querySelector('.alert')).toExist()
-
-          describe 'clicking again on the open button', ->
-            it 'clears the previously created alert', ->
-              tableEditorButton = csvEditorElement.openTableEditorButton
-              click(tableEditorButton)
-
-              expect(csvEditorElement.querySelector('.alert')).not.toExist()
-
-        describe 'changing the delimiter settings', ->
-          beforeEach ->
-            tableEditor = tableEditorElement = null
-            openFixture('semi-colon.csv')
-
-            runs ->
-              csvEditorElement.querySelector('#semi-colon').checked = true
-              csvEditor.onDidOpen ({editor}) ->
-                tableEditor = editor
-
-              tableEditorButton = csvEditorElement.openTableEditorButton
-              click(tableEditorButton)
-
-            waitsFor -> tableEditor?
-
-          it 'now parses the table data properly', ->
-            expect(tableEditor).toBeDefined()
-
-          describe 'when modified and saved', ->
-            beforeEach ->
-              modifyAndSave ->
-                tableEditor.addRow ['Bill', 45, 'male']
-                tableEditor.addRow ['Bonnie', 42, 'female']
-
-            it 'save the new csv content on disk', ->
-              expect(savedContent).toEqual("""
-              name;age;gender
-              Jane;32;female
-              John;30;male
-              Bill;45;male
-              Bonnie;42;female
-              """)
-
-            it 'marks the table editor as saved', ->
-              expect(tableEditor.isModified()).toBeFalsy()
-              expect(csvEditor.isModified()).toBeFalsy()
-
-        describe 'changing the header settings', ->
-          beforeEach ->
-            tableEditor = tableEditorElement = null
-            openFixture('sample.csv')
-
-            runs ->
-              csvEditorElement.querySelector('#header').checked = true
-              csvEditor.onDidOpen ({editor}) ->
-                tableEditor = editor
-
-              tableEditorButton = csvEditorElement.openTableEditorButton
-              click(tableEditorButton)
-
-            waitsFor -> tableEditor?
-
-          it 'now parses the table data properly', ->
-            expect(tableEditor).toBeDefined()
-            expect(tableEditor.getScreenColumnCount()).toEqual(3)
-            expect(tableEditor.getColumns()).toEqual(['name', 'age', 'gender'])
-            expect(tableEditor.getScreenRowCount()).toEqual(2)
-
-          describe 'when modified and saved', ->
-            beforeEach ->
-              modifyAndSave ->
-                tableEditor.addRow ['Bill', 45, 'male']
-                tableEditor.addRow ['Bonnie', 42, 'female']
-
-            it 'save the new csv content on disk', ->
-              expect(savedContent).toEqual("""
-              name,age,gender
-              Jane,32,female
-              John,30,male
-              Bill,45,male
-              Bonnie,42,female
-              """)
-
-            it 'marks the table editor as saved', ->
-              expect(tableEditor.isModified()).toBeFalsy()
-              expect(csvEditor.isModified()).toBeFalsy()
-
-        describe 'changing the row delimiter settings', ->
-          beforeEach ->
-            tableEditor = tableEditorElement = null
-            openFixture('custom-row-delimiter.csv')
-
-            runs ->
-              csvEditorElement.querySelector('atom-text-editor').getModel().setText('::')
-              csvEditorElement.querySelector('#custom-row-delimiter').checked = true
-              csvEditor.onDidOpen ({editor}) ->
-                tableEditor = editor
-
-              tableEditorButton = csvEditorElement.openTableEditorButton
-              click(tableEditorButton)
-
-            waitsFor -> tableEditor?
-
-          it 'now parses the table data properly', ->
-            expect(tableEditor).toBeDefined()
-            expect(tableEditor.getScreenColumnCount()).toEqual(2)
-            expect(tableEditor.getScreenRowCount()).toEqual(2)
-
-          describe 'when modified and saved', ->
-            beforeEach ->
-              modifyAndSave ->
-                tableEditor.addRow ['GHI', 56]
-                tableEditor.addRow ['JKL', 78]
-
-            it 'save the new csv content on disk', ->
-              expect(savedContent).toEqual("ABC,12::DEF,34::GHI,56::JKL,78")
-
-            it 'marks the table editor as saved', ->
-              expect(tableEditor.isModified()).toBeFalsy()
-              expect(csvEditor.isModified()).toBeFalsy()
+    describe 'when the file cannot be parsed with the default', ->
+      beforeEach ->
+        openFixture('invalid.csv')
+
+        runs ->
+          tableEditorButton = csvEditorElement.openTableEditorButton
+          click(tableEditorButton)
+
+        waitsFor -> csvEditorElement.querySelector('.alert')
+
+      it 'displays the error in the settings form', ->
+        expect(csvEditorElement.querySelector('.alert')).toExist()
+
+      describe 'clicking again on the open button', ->
+        it 'clears the previously created alert', ->
+          tableEditorButton = csvEditorElement.openTableEditorButton
+          click(tableEditorButton)
+
+          expect(csvEditorElement.querySelector('.alert')).not.toExist()
+
+    describe 'changing the delimiter settings', ->
+      beforeEach ->
+        openFixture('semi-colon.csv')
+
+        runs ->
+          csvEditorElement.querySelector('#semi-colon').checked = true
+          csvEditor.onDidOpen ({editor}) ->
+            tableEditor = editor
+
+        waitsFor sleep CHANGE_TIMEOUT
+
+        runs ->
+          tableEditorButton = csvEditorElement.openTableEditorButton
+          click(tableEditorButton)
+
+        waitsFor -> tableEditor?
+
+      it 'now parses the table data properly', ->
+        expect(tableEditor).toBeDefined()
+
+      describe 'when modified and saved', ->
+        beforeEach ->
+          modifyAndSave ->
+            tableEditor.addRow ['Bill', 45, 'male']
+            tableEditor.addRow ['Bonnie', 42, 'female']
+
+        it 'save the new csv content on disk', ->
+          expect(savedContent).toEqual("""
+          name;age;gender
+          Jane;32;female
+          John;30;male
+          Bill;45;male
+          Bonnie;42;female
+          """)
+
+        it 'marks the table editor as saved', ->
+          expect(tableEditor.isModified()).toBeFalsy()
+          expect(csvEditor.isModified()).toBeFalsy()
+
+    describe 'changing the header settings', ->
+      beforeEach ->
+        openFixture('sample.csv')
+
+        runs ->
+          csvEditorElement.querySelector('#header').checked = true
+          csvEditor.onDidOpen ({editor}) ->
+            tableEditor = editor
+
+        waitsFor sleep CHANGE_TIMEOUT
+
+        runs ->
+          tableEditorButton = csvEditorElement.openTableEditorButton
+          click(tableEditorButton)
+
+        waitsFor -> tableEditor?
+
+      it 'now parses the table data properly', ->
+        expect(tableEditor).toBeDefined()
+        expect(tableEditor.getScreenColumnCount()).toEqual(3)
+        expect(tableEditor.getColumns()).toEqual(['name', 'age', 'gender'])
+        expect(tableEditor.getScreenRowCount()).toEqual(2)
+
+      describe 'when modified and saved', ->
+        beforeEach ->
+          modifyAndSave ->
+            tableEditor.addRow ['Bill', 45, 'male']
+            tableEditor.addRow ['Bonnie', 42, 'female']
+
+        it 'save the new csv content on disk', ->
+          expect(savedContent).toEqual("""
+          name,age,gender
+          Jane,32,female
+          John,30,male
+          Bill,45,male
+          Bonnie,42,female
+          """)
+
+        it 'marks the table editor as saved', ->
+          expect(tableEditor.isModified()).toBeFalsy()
+          expect(csvEditor.isModified()).toBeFalsy()
+
+    describe 'changing the row delimiter settings', ->
+      beforeEach ->
+        openFixture('custom-row-delimiter.csv')
+
+        runs ->
+          csvEditorElement.querySelector('atom-text-editor').getModel().setText('::')
+          csvEditorElement.querySelector('#custom-row-delimiter').checked = true
+          csvEditor.onDidOpen ({editor}) ->
+            tableEditor = editor
+
+        waitsFor sleep CHANGE_TIMEOUT
+
+        runs ->
+          tableEditorButton = csvEditorElement.openTableEditorButton
+          click(tableEditorButton)
+
+        waitsFor -> tableEditor?
+
+      it 'now parses the table data properly', ->
+        expect(tableEditor).toBeDefined()
+        expect(tableEditor.getScreenColumnCount()).toEqual(2)
+        expect(tableEditor.getScreenRowCount()).toEqual(2)
+
+      describe 'when modified and saved', ->
+        beforeEach ->
+          modifyAndSave ->
+            tableEditor.addRow ['GHI', 56]
+            tableEditor.addRow ['JKL', 78]
+
+        it 'save the new csv content on disk', ->
+          expect(savedContent).toEqual("ABC,12::DEF,34::GHI,56::JKL,78")
+
+        it 'marks the table editor as saved', ->
+          expect(tableEditor.isModified()).toBeFalsy()
+          expect(csvEditor.isModified()).toBeFalsy()


Add specs for enhanced class-list handling

--- spec/file-icons-spec.coffee
+++ spec/file-icons-spec.coffee
@@ -1,3 +1,7 @@
+fs = require 'fs-plus'
+temp = require('temp').track()
+path = require 'path'
+
 DefaultFileIcons = require '../lib/default-file-icons'
 FileIcons = require '../lib/file-icons'

@@ -21,3 +25,45 @@
     FileIcons.resetService()

     expect(FileIcons.getService()).not.toBe(service)
+
+
+  describe 'Class handling', ->
+    [workspaceElement, treeView, files] = []
+
+    beforeEach ->
+      rootDirPath = fs.absolute(temp.mkdirSync('tree-view-root1'))
+
+      for i in [1..3]
+        filepath = path.join(rootDirPath, "file-#{i}.txt")
+        fs.writeFileSync(filepath, "Nah")
+
+      atom.project.setPaths([rootDirPath])
+      workspaceElement = atom.views.getView(atom.workspace)
+      jasmine.attachToDOM(workspaceElement)
+
+      FileIcons.setService
+        iconClassForPath: (path, file) ->
+          [name, id] = path.match(/file-(\d+)\.txt$/)
+          switch id
+            when "1" then 'first second'
+            when "2" then ['first', 'second']
+            when "3" then file.constructor.name
+
+      waitsForPromise ->
+        atom.packages.activatePackage('tree-view')
+
+      runs ->
+        treeView = atom.packages.getActivePackage("tree-view").mainModule.createView()
+        files = workspaceElement.querySelectorAll('li[is="tree-view-file"]')
+
+    afterEach ->
+      temp.cleanup()
+
+    it 'allows multiple classes to be passed', ->
+      expect(files[0].fileName.className).toBe('name icon first second')
+
+    it 'allows an array of classes to be passed', ->
+      expect(files[1].fileName.className).toBe('name icon first second')
+
+    it 'passes a file reference as iconClassForPath\'s second argument', ->
+      expect(files[2].fileName.className).toBe('name icon File')


Don't reopen modal since rendering the modal explicitly fades (and thus causes flicker).

--- core/app/backbone/modules/discussion_modal_on_frontend.coffee
+++ core/app/backbone/modules/discussion_modal_on_frontend.coffee
@@ -56,8 +56,9 @@
   DiscussionModalOnFrontend.openDiscussion = (fact) ->
     Backbone.history.navigate fact.get('url'), false

-    discussionModalContainer = new DiscussionModalContainer
-    FactlinkApp.discussionModalRegion.show discussionModalContainer
+    if !discussionModalContainer
+      discussionModalContainer = new DiscussionModalContainer
+      FactlinkApp.discussionModalRegion.show discussionModalContainer
     discussionModalContainer.mainRegion.show new DiscussionView model: fact

   DiscussionModalOnFrontend.closeDiscussion = ->


removed console.log

removed console.log

--- src/views/HeaderView.coffee
+++ src/views/HeaderView.coffee
@@ -36,7 +36,6 @@
     nMax = @model.getMaxLength()
     stepSize = @g.zoomer.get("stepSize")
     hidden = @g.columns.get "hidden"
-    console.log hidden

     while n < nMax
       if hidden.indexOf(n) >= 0


Add a notice on file-redirection

Add a notice on file-redirection

--- lib/ghc-modi-process.coffee
+++ lib/ghc-modi-process.coffee
@@ -29,10 +29,15 @@
         if code!=0
           # no redirect support
           @backend=new GhcModiProcessTemp
-          console.log 'temp'
         else
           @backend=new GhcModiProcessRedirect
-          console.log 'redirect'
+          m="Haskell-ghc-mod:
+             Copy of this message can be found in dev. console.
+             Found master ghc-mod.
+             Thank you for testing! Bear in mind that this is highly
+             experimental option. Please report any bugs."
+          atom.notifications.addInfo m
+          console.log m
         for k,v of @commandQueues
           @runQueuedCommands k
     .onWillThrowError (error, handle) ->


Removed gruntfile

Removed gruntfile

--- gruntfile.coffee
+++ gruntfile.coffee
@@ -1,61 +0,0 @@
-module.exports = (grunt) ->
-  grunt.initConfig
-    karma:
-      options:
-        configFile: 'karma.conf.coffee'
-        runnerPort: 9999
-        port: 9998
-      ci:
-        singleRun: true
-        reporters: ['dots']
-      dev:
-        background: true
-
-    simplemocha:
-      options:
-        ui: 'bdd',
-        reporter: 'dot',
-        ignoreLeaks: false
-      server:
-        src: ['test/server/*.coffee']
-    watch:
-      karma:
-        files: [
-          'lib/**/*.js'
-          'test/browser/*.coffee'
-          'test/helpers/*.coffee'
-        ]
-        tasks: ['karma:dev:run']
-      mocha:
-        files: [
-          'lib/**/*.js'
-          'test/server/*.coffee'
-          'test/helpers/*.coffee'
-        ]
-        tasks: ['test:server']
-
-  # Load NPM Tasks
-  grunt.loadNpmTasks 'grunt-karma'
-  grunt.loadNpmTasks 'grunt-simple-mocha'
-  grunt.loadNpmTasks 'grunt-contrib-watch'
-
-  # Register Tasks
-  grunt.registerTask 'test:browser', ['server', 'karma:ci']
-  grunt.registerTask 'test:server', ['simplemocha:server']
-  grunt.registerTask 'test', ['test:server', 'test:browser']
-
-  grunt.registerTask 'server', 'Start a server to test clients', ->
-    done = this.async()
-    server = require('./test/helpers/server')({log: !!grunt.cli.options.debug})
-    server.listen(3000)
-    .on('listening', done)
-    .on 'error', (err) ->
-      if (err.code is 'EADDRINUSE')
-        grunt.fatal('Port 3000 is already in use by another process.')
-      else
-        grunt.fatal(err)
-
-  # Default Task
-  grunt.registerTask 'default', ['server', 'karma:dev:start', 'watch']
-
-


Update gruntcoffee.js

Update gruntcoffee.js

--- gruntfile.coffee
+++ gruntfile.coffee
@@ -1,4 +1,4 @@
-module.exports = (grunt) ->
+module.exports = (grunt)->

   require('load-grunt-tasks')(grunt)


Configure pylama

Configure pylama

--- atom/dot-atom/config.cson
+++ atom/dot-atom/config.cson
@@ -72,6 +72,7 @@
       "linter-pylint"
       "linter-flake8"
       "markdown-preview-plus-opener"
+      "linter-pydocstyle"
     ]
     packagesWithKeymapsDisabled: []
     telemetryConsent: "no"
@@ -96,14 +97,17 @@
       "source.pweave.md"
       "text.md"
     ]
+  "linter-pydocstyle": {}
   "linter-pylama":
+    configFileLoad: "Use pylama config"
     executablePath: "~/.pyenv/shims/pylama, pylama, pylama.exe"
     ignoreErrorsAndWarnings: "D203,D213,W0621"
     interpreter: "~/.pyenv/shims/python, python, python.exe"
     pylamaVersion: "external"
-    usePylint: true
+    useIsort: true
   "linter-ui-default":
     decorateOnTreeView: "Files and Directories"
+    showPanel: true
   "markdown-preview-plus":
     enablePandoc: true
     grammars: [


don't add new box

don't add new box

--- code/serv.coffee
+++ code/serv.coffee
@@ -177,22 +177,12 @@
   User.findOne {apikey: req.body.apikey}, (err, user) ->
     console.tick "found user (again) #{boxname}"
     return res.send 404, {error: "User not found" } unless user?
-    Box.findOne {name: boxname}, (err, box) ->
-      console.tick "checked box existence #{boxname}"
-      if box
-        return res.send {error: "Box already exists"}
-      # TODO: multiple users per box here? what about contexts?
-      new Box({users: [user.shortName], name: boxname}).save (err) ->
-        console.tick "created database entity: box #{boxname}"
-        if err
-          console.log "Creating box: #{err} "
-          return res.send 404, {error: "Unknown error"}
-        exports.unix_user_add boxname, (err, stdout, stderr) ->
-          console.tick "added unix user #{boxname}"
-          any_stderr = stderr is not ''
-          console.log "Error adding user: #{err} #{stderr}" if err? or any_stderr
-          return res.send {error: "Unable to create box"} if err? or any_stderr
-          return res.send {status: "ok"}
+    exports.unix_user_add boxname, (err, stdout, stderr) ->
+      console.tick "added unix user #{boxname}"
+      any_stderr = stderr is not ''
+      console.log "Error adding user: #{err} #{stderr}" if err? or any_stderr
+      return res.send {error: "Unable to create box"} if err? or any_stderr
+      return res.send {status: "ok"}

 myCheckIdent = (req, res, next) ->
   if req.ip is "88.211.55.91"
@@ -240,13 +230,13 @@
   file = req.files.file
   if ! file
     return res.send 400, { error: "no file" }
-
+
   # Remove all characters apart from a few select safe ones.
   file.name = file.name.replace /[^.a-zA-Z0-9_+-]/g, ''
   child_process.exec "mv #{file.path} #{dir}/#{file.name}", ->
     child_process.exec "chown #{boxname}: #{dir}/#{file.name}", ->
       next = req.body.next || "/"
-      # TODO: there must be a better way of doing this,
+      # TODO: there must be a better way of doing this,
       # perhaps easyXDM with file upload + metadata calls
       # If there's a # in the URL, read the JSON object out
       if next.indexOf('#') > -1


'component test' auto-installs if necessary

--- component.coffee
+++ component.coffee
@@ -157,6 +157,10 @@
   </script>
   """

+listTests = (tests, host, port) ->
+  for test in tests
+    console.log("http://#{host}:#{port}/#{test.replace(/\.js$/, "")}")
+
 commands =

   install: () ->
@@ -170,22 +174,17 @@
       src = join(component.path, sourceDirectory(component.path))
       name = componentName(null, component.json)
       installTo componentsDirName, argv.link, src, name
+    if isComponent()
+      installTo componentsDirName, argv.link, 'src', componentName()

   test: () ->
     port = process.env.PORT || 1335
     host = process.env.HOST || "localhost"
-    listTests = (tests) ->
-      for test in tests
-        console.log("http://#{host}:#{port}/#{test.replace(/\.js$/, "")}")
     components = allComponents()
     testDirName = testDirectory()
     staticDirName = join (if isComponent() then testDirName else ""), "static"
-    return listTests(discoverTests(staticDirName)) if argv.list
-    if isComponent() and not exists testDirName
-      commands.install()
-    if not exists join(staticDirName, "components", componentName())
-      commands.install()
-      installTo join(staticDirName, "components"), argv.link
+    return listTests(discoverTests(staticDirName), host, port) if argv.list
+    commands.install()
     tests = discoverTests staticDirName
     extraHtml = getTestTemplate() + "\n" + stringBundlesAsRequirejsModule()
     testServer
@@ -193,7 +192,7 @@
       .listen(port, host)
     console.log "serving at http://#{host}:#{port}"
     console.log "available tests:"
-    listTests tests
+    listTests tests, host, port

   template: (cmd, args...) ->
     config = getConfig()


ensured `arguments` in each scope

ensured `arguments` in each scope

--- scope.coffee
+++ scope.coffee
@@ -6,7 +6,7 @@
 # with the outside.

 # Import the helpers we plan to use.
-{extend} = require './helpers'
+{extend, last} = require './helpers'

 exports.Scope = class Scope

@@ -17,9 +17,8 @@
   # as well as a reference to the **Expressions** node is belongs to, which is
   # where it should declare its variables, and a reference to the function that
   # it wraps.
-  constructor: (parent, expressions, method) ->
-    [@parent, @expressions, @method] = [parent, expressions, method]
-    @variables = {}
+  constructor: (@parent, @expressions, @method) ->
+    @variables = {'arguments'}
     if @parent
       @garbage = @parent.garbage
     else
@@ -33,7 +32,8 @@
   # Return to the previous garbage level and erase referenced temporary
   # variables in current level from scope.
   endLevel: ->
-    (@variables[name] = 'reuse') for name in @garbage.pop() when @variables[name] is 'var'
+    vars = @variables
+    (vars[name] = 'reuse') for name in @garbage.pop() when vars[name] is 'var'

   # Look up a variable name in lexical scope, and declare it if it does not
   # already exist.
@@ -57,8 +57,8 @@
   # walks up to the root scope.
   check: (name, options) ->
     immediate = Object::hasOwnProperty.call @variables, name
-    return immediate if immediate or (options and options.immediate)
-    !!(@parent and @parent.check(name))
+    return immediate if immediate or options?.immediate
+    !!@parent?.check name

   # Generate a temporary variable name at the given index.
   temporary: (type, index) ->
@@ -73,7 +73,7 @@
     index = 0
     index++ while @check(temp = @temporary type, index) and @variables[temp] isnt 'reuse'
     @variables[temp] = 'var'
-    @garbage[@garbage.length - 1].push temp if @garbage.length
+    last(@garbage).push temp if @garbage.length
     temp

   # Ensure that an assignment is made at the top of this scope
@@ -84,7 +84,7 @@
   # Does this scope reference any variables that need to be declared in the
   # given function body?
   hasDeclarations: (body) ->
-    body is @expressions and @any (k, val) -> val is 'var' or val is 'reuse'
+    body is @expressions and @any (k, val) -> val in ['var', 'reuse']

   # Does this scope reference any assignments that need to be declared at the
   # top of the given function body?
@@ -93,7 +93,7 @@

   # Return the list of variables first declared in this scope.
   declaredVariables: ->
-    (key for key, val of @variables when val is 'var' or val is 'reuse').sort()
+    (key for key, val of @variables when val in ['var', 'reuse']).sort()

   # Return the list of assignments that are supposed to be made at the top
   # of this scope.


Added a basic benchmark. Resolves #15

Added a basic benchmark. Resolves #15

--- test/wordnet_benchmark.coffee
+++ test/wordnet_benchmark.coffee
@@ -1,7 +1,7 @@
-NanoTimer = require('nanotimer')
+microtime = require('microtime')
 async = require('async')

-Wordnet = require('../lib/wordnet')
+Wordnet = require('../src/wordnet')
 wordnet = new Wordnet()

 testText = """
@@ -338,11 +338,9 @@
 while match = matcher.exec(string)
   tokens.push match[0]

-timer = new NanoTimer()
-
 testTokens = (tokens, callback) ->
   lookup = (token, done) ->
-    wordnet.lookup token, (err, result) -> done(err)
+    wordnet.lookup token, (result) -> done()
   async.each tokens, lookup, callback

 testFunction = (tokens, callback) ->
@@ -352,6 +350,14 @@
   async.each [1..1000], itemFn, () ->
     callback()

-console.log "Starting run"
-timer.time testFunction, [tokens], 'u', (time) ->
-  console.log("It took " + time + " microseconds to process #{tokens.length} tokens #{count} times")
+time = (fn) ->
+  startTime = microtime.now()
+  end = () ->
+    endTime = microtime.now()
+    elapsed = endTime - startTime
+    console.log "It took " + elapsed + " microseconds to process #{tokens.length} tokens #{count} times"
+
+  fn(end)
+
+time (callback) ->
+  testFunction tokens, callback


Use the async .check() in SpellCheckTask

--- lib/spell-check-task.coffee
+++ lib/spell-check-task.coffee
@@ -5,7 +5,7 @@
   @handler: null
   @jobs: []

-  constructor: (@task) ->
+  constructor: (@manager) ->
     @id = idCounter++

   terminate: ->
@@ -22,16 +22,14 @@
     @constructor.removeFromArray(@constructor.jobs, (j) -> j.args.id is @id)

     # Create an job that contains everything we'll need to do the work.
-    job = {
-      task: @task,
-      callbacks: [onDidSpellCheck],
-      args: {
-        id: @id,
-        projectPath,
-        relativePath,
+    job =
+      manager: @manager
+      callbacks: [onDidSpellCheck]
+      args:
+        id: @id
+        projectPath: projectPath
+        relativePath: relativePath
         text: buffer.getText()
-      }
-    }

     # If we already have a job for this work piggy-back on it with our callback.
     return if @constructor.piggybackExistingJob(job)
@@ -41,10 +39,9 @@
     @constructor.startNextJob() if @constructor.jobs.length is 1

   @piggybackExistingJob: (newJob) ->
-    if (@jobs.length > 0)
-      for i in [0..@jobs.length-1]
-        job = @jobs[i]
-        if (@isDuplicateRequest(job, newJob))
+    if @jobs.length > 0
+      for job in @jobs
+        if @isDuplicateRequest(job, newJob)
           job.callbacks = job.callbacks.concat(newJob.callbacks)
           return true
     return false
@@ -62,11 +59,8 @@

   @startNextJob: ->
     job = @jobs[0]
-    job.task?.start job.args, @dispatchMisspellings
+    job.manager.check(job.args, job.args.text).then (results) =>
+      @removeFromArray(@jobs, (j) -> j.args.id is job.args.id)
+      callback(results.misspellings) for callback in job.callbacks

-  @dispatchMisspellings: (data) =>
-    job = @removeFromArray(@jobs, (j) -> j.args.id is data.id)
-    for callback in job.callbacks
-      callback(data.misspellings)
-
-    @startNextJob() if @jobs.length > 0
+      @startNextJob() if @jobs.length > 0


Code consistency

Code consistency

--- steam.coffee
+++ steam.coffee
@@ -74,7 +74,7 @@

     robot.respond /steam status (.*)/i, (msg) ->
         summary = (steamID) ->
-            steam_request msg, "/ISteamUser/GetPlayerSummaries", steamids: steamID, (object) ->
+            steamRequest msg, "/ISteamUser/GetPlayerSummaries", steamids: steamID, (object) ->
                 player = object.response.players[0]
                 status = if player.communityvisibilitystate is 1 then 'Unavailable (Private)' else personaStates[player.personastate]
                 lastOnline = moment.unix(player.lastlogoff).fromNow()
@@ -96,7 +96,7 @@
             matches_requested: 5

         history = (params, type = 'Steam ID') ->
-            steam_request msg, "/IDOTA2Match_570/GetMatchHistory", params, (object) ->
+            steamRequest msg, "/IDOTA2Match_570/GetMatchHistory", params, (object) ->
                 if object.result.status is 15
                     msg.reply "The #{type} you have entered (\"#{params.original}\") does not exist or it does not have match history enabled."
                     return
@@ -125,7 +125,7 @@
                 history params, 'profile URL'

     robot.respond /dota match (\d+)\s*(.*)?/i, (msg) ->
-        steam_request msg, "/IDOTA2Match_570/GetMatchDetails", match_id: msg.match[1], (object) ->
+        steamRequest msg, "/IDOTA2Match_570/GetMatchDetails", match_id: msg.match[1], (object) ->
             match = object.result
             start = moment.unix(match.start_time).fromNow()
             duration = moment.duration(match.duration, 'seconds').minutes()
@@ -157,7 +157,7 @@
         for steamID, user of steamData()
             return handler steamID if user.url is customURL

-        steam_request msg, "/ISteamUser/ResolveVanityURL", vanityurl: customURL, (object) ->
+        steamRequest msg, "/ISteamUser/ResolveVanityURL", vanityurl: customURL, (object) ->
             if object.response.success is 42
                 msg.reply "The custom URL you have entered (\"#{customURL}\") does not exist."
                 return
@@ -188,7 +188,7 @@
     for status, tower in "00000000000#{(+dec).toString(2)}".slice(-11).split('')
         if parseInt(status) then towers[tower] else continue

-steam_request = (msg, endpoint, params = {}, handler, version = 1) ->
+steamRequest = (msg, endpoint, params = {}, handler, version = 1) ->
     params.key = STEAM_API_KEY

     msg.http("http://api.steampowered.com#{endpoint}/v#{version}/")


Tombstones can't be updated

Tombstones can't be updated

Fixes test "Posting an update should not be possible for a deleted item".

--- src/local/operations.coffee
+++ src/local/operations.coffee
@@ -521,6 +521,9 @@
                                     cb4 err, item
                     , (oldItem, cb4) =>
                         if oldItem?
+                            if oldItem.name is 'deleted-entry'
+                                return cb4 new errors.NotAcceptable "You can't update a deleted item"
+
                             # Only the original author can update an item
                             itemActor = oldItem.getChild("author")?.getChild("uri")?.getText()
                             if itemActor is "acct:#{@req.actor}"


Move online status and last login out of profile and into a ‘status’ record.

--- user_status.coffee
+++ user_status.coffee
@@ -14,13 +14,13 @@

   if UserSessions.find(userId: userId).count() is 0
     Meteor.users.update userId,
-      $set: {'profile.online': false}
+      $set: {'status.online': false}
   return

 # Clear any online users on startup (they will re-add themselves)
 Meteor.startup ->
   Meteor.users.update {},
-    $unset: { "profile.online": null }
+    $unset: { "status.online": null }
   , {multi: true}

 # pub/sub trick as referenced in http://stackoverflow.com/q/10257958/586086
@@ -52,7 +52,7 @@
   UserStatus.emit("sessionLogin", userId, sessionId, ipAddr, dateMs)

   Meteor.users.update userId,
-    $set: {'profile.online': true, 'profile.lastLogin': dateMs}
+    $set: {'status.online': true, 'status.lastLogin': dateMs}

   # Remove socket on close
   @_session.socket.on "close", Meteor.bindEnvironment ->


Fix tree view selector for Search in Directory context menu item

--- menus/find-and-replace.cson
+++ menus/find-and-replace.cson
@@ -22,7 +22,7 @@
 ]

 'context-menu':
-  '.tree-view > li.directory': [
+  '.tree-view li.directory': [
     { 'label': 'Search in Directory', 'command': 'project-find:show-in-current-directory' }
   ]
   '.path-details.list-item': [


bounded documentation

bounded documentation


Add Template model Created a Template model for storing templates in the database. Adding this commit as a starting point before diving into API changes.

--- server/models/template.coffee
+++ server/models/template.coffee
@@ -36,4 +36,5 @@

 Template.statics.renameRoutes = (oldName, newName, callback) ->
   Route.update({ template: oldName }, { template: newName }, callback)
+
 module.exports = db.model('Template', Template)


runtime: Output protocol errors by default


Prevent infinite looping when slug is an empty string.

--- client/publication.coffee
+++ client/publication.coffee
@@ -194,7 +194,9 @@

   return unless publication

-  unless Session.equals 'currentPublicationSlug', publication.slug
+  # currentPublicationSlug is undefined if slug is not present in URL, so we use
+  # undefined when publication.slug is empty string to prevent infinite looping
+  unless Session.equals 'currentPublicationSlug', (publication.slug or undefined)
     Meteor.Router.to Meteor.Router.publicationPath publication._id, publication.slug
     return


Make Atom custom keymaps sparser (Atom has better defaults now)

--- home/.atom/keymap.cson
+++ home/.atom/keymap.cson
@@ -33,10 +33,6 @@

 'atom-text-editor':
   'ctrl-d': 'editor:delete-line'
-  'ctrl-shift-d': 'editor:duplicate-lines'
-  'ctrl-b': 'bracket-matcher:go-to-matching-bracket'
   'ctrl-shift-up': 'editor:add-selection-above'
   'ctrl-shift-down': 'editor:add-selection-below'
-  'ctrl-shift-z': 'core:redo'
   'alt-d': 'find-and-replace:select-next'
-  'ctrl-e': 'emmet:expand-abbreviation'


FIX: empty menu bug

FIX: empty menu bug

--- src/menu/menubuilder.coffee
+++ src/menu/menubuilder.coffee
@@ -14,6 +14,7 @@

     addNode: (label, callback, data) ->
       style = data.style if data?
+      @_nodes = [] unless @_nodes?
       @_nodes.push {label: label, callback: callback, style: style}

     buildDOM: ->
@@ -32,7 +33,6 @@

       menuUl = document.createElement "ul"
       menuUl.className = "dropdown-menu"
-      menuUl.style.fontSize = @g.zoomer.get "menuItemFontsize"

       # dropdown menu
       for node in nodes
@@ -42,7 +42,8 @@
         for key,style of node.style
           li.style[key] = style
         li.addEventListener "click", node.callback
-        li.style.lineHeight = @g.zoomer.get "menuItemLineHeight"
+        if @g?
+          li.style.lineHeight = @g.zoomer.get "menuItemLineHeight"

         menuUl.appendChild li

@@ -55,9 +56,11 @@
       displayedButton.className = "biojs_msa_menubar_alink"

       # tiny style
-      displayedButton.style.fontSize = @g.zoomer.get "menuFontsize"
-      displayedButton.style.marginLeft = @g.zoomer.get "menuMarginLeft"
-      displayedButton.style.padding = @g.zoomer.get "menuPadding"
+      if @g?
+        menuUl.style.fontSize = @g.zoomer.get "menuItemFontsize"
+        displayedButton.style.fontSize = @g.zoomer.get "menuFontsize"
+        displayedButton.style.marginLeft = @g.zoomer.get "menuMarginLeft"
+        displayedButton.style.padding = @g.zoomer.get "menuPadding"

       jbone(displayedButton).on "click", (e) =>
         @_showMenu e,menu,displayedButton


Made key for script more specific.

Made key for script more specific.

--- home/.atom/keymap.cson
+++ home/.atom/keymap.cson
@@ -20,7 +20,7 @@
 '.workspace .editor:not(.mini), .workspace':
   'cmd-<': 'unset!'

-'.platform-darwin .editor':
+'.platform-darwin .editor, .workspace':
   'ctrl-cmd-i': 'script:run'

 '.platform-darwin .editor':


correct syntax error

correct syntax error

--- mechanics/001-binary-stars/orbit.coffee
+++ mechanics/001-binary-stars/orbit.coffee
@@ -23,7 +23,7 @@
        context.closePath()

    orbit.paint = (data) ->
-       x1, y1, vx1, vy1, x2, y2, vx2, vy2 = data
+       [x1, y1, vx1, vy1, x2, y2, vx2, vy2] = data
        context.clearRect(0, 0, 512, 512)
        circle(s(x1), s(y1), 6, "blue")
        circle(s(x2), s(y2), 4, "yellow")


don't override server documentation

don't override server documentation

--- app/server.coffee
+++ app/server.coffee
@@ -58,7 +58,9 @@
         value = function_name: n, summary: d.signature, complete: build_function_doc
       else
         value = summary: '(undocumented)'
-      Documentation.register_documentation ['server', 'functions', n], value
+      key = ['server', 'functions', n]
+      unless Documentation.get_documentation(key)
+        Documentation.register_documentation(key, value)

     _.each _.sortBy(server_option_names, _.identity), (n) ->
       d = docs.parameter_docs[n]


Rename nodeActive with node_hovered & call onTick only when force layout is stopped

--- app/frontend/javascripts/views/visualization-canvas.js.coffee
+++ app/frontend/javascripts/views/visualization-canvas.js.coffee
@@ -3,9 +3,9 @@

 class VisualizationCanvas extends VisualizationCanvasBase

-  context:    null
-  quadtree:   null
-  nodeActive: null
+  context:      null
+  quadtree:     null
+  node_hovered: null

   setupCanvas: ->
@@ -47,20 +47,25 @@
     mouse = d3.mouse @canvas.node()
     @setQuadtree()
     node = @quadtree.find mouse[0], mouse[1]
-    if @nodeActive and @nodeActive.id == node.id
+    if @node_hovered and @node_hovered.id == node.id
       return
     @data_nodes.forEach (d) -> d.disabled = true
-    @nodeActive = node
-    @nodeActive.disabled = false
+    @node_hovered = node
+    @areNodesRelated(d, o)
+    @node_hovered.disabled = false
     @updateNodesColorValue()
-    @onTick()
+    # update canvas if force layout stoped
+    if @force.alpha() < @force.alphaMin()
+      @onTick()

   onCanvasLeave: (e) =>
     console.log 'onCanvasOut'
-    @nodeActive = null
+    @node_hovered = null
     @data_nodes.forEach (d) -> d.disabled = false
     @updateNodesColorValue()
-    @onTick()
+    # update canvas if force layout stoped
+    if @force.alpha() < @force.alphaMin()
+      @onTick()

   updateNodes: ->
@@ -150,7 +155,7 @@
       @context.closePath()

     # Draw Labels
-    if @parameters.showNodesLabel or @nodeActive
+    if @parameters.showNodesLabel or @node_hovered
       @nodes.each (d, i, n) =>
         if !d.disabled
           node = d3.select(n[i])


docs(travis): test 2

docs(travis): test 2

--- docs/scripts/deploy-ci/tasks/disable-host-key-checking.coffee
+++ docs/scripts/deploy-ci/tasks/disable-host-key-checking.coffee
@@ -48,7 +48,7 @@
 	# run task
 	# ========
 	runTask = async ->
-		await temp()
+		# await temp()
 		await ensureFile()
 		disabled = await isDisabled()
 		await disableChecking disabled.addBeginningNewLine unless disabled.disabled


MemberRemoved: set oldOwner info into machines

--- workers/social/lib/social/models/computeproviders/handlers/memberremoved.coffee
+++ workers/social/lib/social/models/computeproviders/handlers/memberremoved.coffee
@@ -33,13 +33,21 @@
         log 'Failed to delete stack:', err  if err

-setOwnerOfMachine = (machine, { account, user }) ->
+setOwnerOfMachine = (machine, { account, user, oldOwner }) ->

   # Update machine ownership to the admin who kicked the member
   machine.addUsers {
     targets: [ user ], asOwner: yes, sudo: yes
   }, (err) ->
     log 'Failed to change ownership of machine:', err  if err
+
+    machine.update {
+      $set: {
+        'meta.oldOwner': oldOwner
+      }
+    }, (err) ->
+      log 'Failed to set oldOwner of machine:', err  if err
+

   # Update workspace ownerships
   JWorkspace = require '../../workspace'
@@ -70,6 +78,7 @@
       return  if not owner

       # otherwise we move the ownership of the machine to the requester
+      requester.oldOwner = user.getAt 'username'
       setOwnerOfMachine machine, requester


Remove built-in error handler

Remove built-in error handler

--- src/player_internals.coffee
+++ src/player_internals.coffee
@@ -25,10 +25,10 @@
     blob = new Blob([source])

     @worker = new Worker(URL.createObjectURL(blob))
-    @worker.onerror = @_workerError
     @worker.onmessage = @_workerMessage

     if window.yieldWorker
+      # Allows us to add a native error handler in Atom
       window.yieldWorker(@worker)

   pop: ->
@@ -55,11 +55,6 @@
       else
         console.log "Worker message received. Arguments:"
         console.log message.data
-
-  _workerError: (error) =>
-    console.log "Worker Error: #{error.message}. Line #{error.lineno}"
-    console.log error
-    error.preventDefault()

 class @AudioWrapper


Added coveralls badge

Added coveralls badge

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -1,4 +1,14 @@
 module.exports = ( grunt ) ->
+
+    grunt.loadNpmTasks "grunt-contrib-clean"
+    grunt.loadNpmTasks "grunt-contrib-compress"
+    grunt.loadNpmTasks "grunt-contrib-copy"
+    grunt.loadNpmTasks "grunt-contrib-jshint"
+    grunt.loadNpmTasks "grunt-contrib-uglify"
+    grunt.loadNpmTasks "grunt-contrib-yuidoc"
+    grunt.loadNpmTasks "grunt-ftp-deploy"
+    grunt.loadNpmTasks "grunt-karma"
+    grunt.loadNpmTasks "grunt-processhtml"

     grunt.initConfig

@@ -139,18 +149,6 @@
                     "<%= pkg.name %>-<%= pkg.version %>-docs.zip"
                 ]

-    #  Load all the task modules we need.
-    #
-    grunt.loadNpmTasks "grunt-contrib-clean"
-    grunt.loadNpmTasks "grunt-contrib-compress"
-    grunt.loadNpmTasks "grunt-contrib-copy"
-    grunt.loadNpmTasks "grunt-contrib-jshint"
-    grunt.loadNpmTasks "grunt-contrib-uglify"
-    grunt.loadNpmTasks "grunt-contrib-yuidoc"
-    grunt.loadNpmTasks "grunt-ftp-deploy"
-    grunt.loadNpmTasks "grunt-karma"
-    grunt.loadNpmTasks "grunt-processhtml"
-
     grunt.registerTask(
         "common"
         [
@@ -183,8 +181,6 @@
         ]
     )

-    #  Upload dist to baijs.nl
-    #
     grunt.registerTask(
         "ftp"
         [


fix test task

fix test task

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -53,18 +53,16 @@

     release: {}

-  grunt.registerTask 'loadTasks', grunt.loadTasks
+  grunt.registerTask 'run_this_plugin', ->
+    grunt.loadTasks 'tasks'
+    grunt.task.run ['flex_svg']

-  grunt.registerTask 'build', [
+  grunt.registerTask 'test', [
     'clean'
     'jshint'
     'es6transpiler'
-    'loadTasks:tasks'
-  ]
-
-  grunt.registerTask 'test', [
-    'flex_svg'
+    'run_this_plugin'
     'mochaTest'
   ]

-  grunt.registerTask 'default', ['build', 'test']
+  grunt.registerTask 'default', ['test']


When running watch we would like to see the output

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -26,6 +26,12 @@
         'generateJs'
         'generateAsseticAssets'
       ]
+      watch:
+        options:
+          logConcurrentOutput: true
+        tasks: [
+          'watch'
+        ]

     # Compass configuration
     compass:


Grunt: Re-align comments

Grunt: Re-align comments

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -5,11 +5,11 @@
     coffee:
       default:
         files: [
-          expand: true     # Enable dynamic expansion.
-          cwd: 'src/'      # Src matches are relative to this path.
+          expand: true         # Enable dynamic expansion.
+          cwd: 'src/'          # Src matches are relative to this path.
           src: ['**/*.coffee'] # Actual pattern(s) to match.
-          dest: 'lib/'     # Destination path prefix.
-          ext: '.js'       # Dest filepaths will have this extension.
+          dest: 'lib/'         # Destination path prefix.
+          ext: '.js'           # Dest filepaths will have this extension.
         ]

     watch:


Make connect show only .html files and folders

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -21,7 +21,19 @@

     connect:
-      uses_defaults: {}
+      server:
+        options:
+          port: 8000,
+          base: ''
+          middleware: (connect, options) ->
+            return [
+              # Serve static files.
+              connect.static(options.base)
+              # Show only html files and folders.
+              connect.directory(options.base, { hidden:false, icons:true, filter:(file) ->
+                return /\.html/.test(file) || !/\./.test(file);
+              })
+            ]

     copy:
@@ -265,4 +277,4 @@
   @registerTask( 'publish',    [ 'jshint', 'concat:js', 'uglify', 'stylus', 'jade:publish' ])

   # copy images from /blocks to /publish and then compress them
-  @registerTask( 'publish_img', [ 'clean', 'copy', 'imagemin' ])+  @registerTask( 'publish_img', [ 'clean', 'copy', 'imagemin' ])


Add watcher for package.json to trigger npm install on changes

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -109,10 +109,14 @@
         options:
           reload: true

+      npm:
+        files: ['package.json']
+        tasks: ['npm:install']
+
     growl:
       jasmine_success:
         title: 'Jasmine Tests'
-        message: 'All test passed'
+        message: 'All tests passed'

       jasmine_failure:
         title: 'Jasmine Tests'
@@ -154,7 +158,7 @@
     .fail ->
       done()

-  grunt.registerTask 'test', 'Run npm tests', ->
+  grunt.registerTask 'npm:test', 'Run npm tests', ->
     done = @async()
     run('npm test')
     .then ->
@@ -165,11 +169,21 @@
       grunt.task.run 'growl:jasmine_failure'
       done false

+  grunt.registerTask 'npm:install', 'Run npm tests', ->
+    done = @async()
+    run('npm install')
+    .then ->
+      console.log "Package installed #{'✓'.green}"
+      done true
+    .fail (reason) ->
+      console.log reason
+      done false
+
   grunt.registerTask('all', [
     'coffee:glob_to_multiple'
     'coffee:build'
     'uglify'
-    'test'
+    'npm:test'
     'biscotto'
     'coffee:demos'
     'extend:biscotto'


Fixed version substitution to handle pre-releases

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -60,7 +60,7 @@
         src: ['index.html']
         overwrite: true
         replacements: [
-          { from: /v[0-9]+\.[0-9]+\.[0-9]+/g, to: 'v<%= pkg.version %>' }
+          { from: /v[0-9]+\.[0-9]+\.[0-9]+(-\w+)?/g, to: 'v<%= pkg.version %>' }
         ]

   # These plugins provide necessary tasks.


Add requireConfigFile reference in jasmine options

Like the requirejs configuration options, this now points to the
cilantro/main module as the default configuration options for
requirejs.

Signed-off-by: Byron Ruth <e9d71f5ee7c92d6dc9e92ffdad17b8bd49418f98@devel.io>

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -259,20 +259,16 @@
             local:
                 options:
                     templateOptions:
+                        requireConfigFile: '<%= localDir %>/js/cilantro/main.js'
                         requireConfig:
                             baseUrl: '<%= localDir %>/js'
-                            config:
-                                tpl:
-                                    variable: 'data'

             dist:
                 options:
                     templateOptions:
+                        requireConfigFile: '<%= distDir %>/js/cilantro/main.js'
                         requireConfig:
                             baseUrl: '<%= distDir %>/js'
-                            config:
-                                tpl:
-                                    variable: 'data'

         amdcheck:
             local:


chore(Gruntfile): remove CoffeeScript setting for generating map files

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -3,8 +3,6 @@
     pkg: grunt.file.readJSON('package.json')

     coffee:
-      options:
-        sourceMap: true
       glob_to_multiple:
         expand: true
         cwd: 'src'


and concat the files as well

and concat the files as well

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -64,4 +64,4 @@

   grunt.registerTask 'default', ['coffee:app', 'shell', 'concat', 'connect:server', 'watch']
   grunt.registerTask 'compile', ['coffee:app', 'shell', 'concat']
-  grunt.registerTask 'test', ['coffee', 'mocha_phantomjs']
+  grunt.registerTask 'test', ['coffee', 'concat', 'mocha_phantomjs']


Personal VM.

Personal VM.

--- client/landing/app/MainApp/VirtualizationController.coffee
+++ client/landing/app/MainApp/VirtualizationController.coffee
@@ -113,7 +113,7 @@
     canCreatePersonalVM = "member" in KD.config.roles

     # Take this to a better place, possibly to payment controller.
-    makePayment = (type, host, callback)->
+    makePayment = (type, planCode, callback)->
       if type is 'shared'
         group = KD.singletons.groupsController.getCurrentGroup()
         group.checkPayment (err, payments)->
@@ -132,6 +132,7 @@
               delete data.cardCV

               modal = createAccountPaymentMethodModal data, (newData, onError, onSuccess)->
+                newData.plan = planCode
                 group.makePayment newData, (err, subscription)->
                   if err
                     onError err
@@ -143,8 +144,24 @@
             KD.singletons.vmController.createGroupVM type, vmCreateCallback
             callback()
       else
-        KD.singletons.vmController.createGroupVM type, vmCreateCallback
-        callback()
+        _createUserVM = (cb)->
+          KD.remote.api.JRecurlyPlan.getPlanWithCode planCode, (err, plan)->
+            plan.subscribe {}, (err, subscription)->
+              return cb err  if cb and err
+              KD.singletons.vmController.createGroupVM type, vmCreateCallback
+              cb?()
+        KD.remote.api.JRecurlyPlan.getUserAccount (err, account)->
+          if err
+            modal = createAccountPaymentMethodModal {}, (newData, onError, onSuccess)->
+              newData.plan = planCode
+              KD.remote.api.JRecurlyPlan.setUserAccount newData, (err, result)->
+                if err
+                  onError err
+                else
+                  onSuccess result
+                  _createUserVM callback
+          else
+            _createUserVM()

     vmCreateCallback  = (err, vm)->
       if err


Remove console log here.

Remove console log here.

--- js/flight.coffee
+++ js/flight.coffee
@@ -122,9 +122,7 @@
       if @iScrollInstance? then @iScrollInstance.destroy(); @iScrollInstance = null

       window.setTimeout =>
-        @iScrollInstance = new iScroll(@targetPanel.getElementsByClassName('wrapper')[0].id)
-        console.log @targetPanel.getElementsByClassName('wrapper')[0].id
-
+        @iScrollInstance = new iScroll(@targetPanel.getElementsByClassName('wrapper')[0].id)
       , 0

    # displays pages when transiton is false


made mincha earlier on Ereb YT

made mincha earlier on Ereb YT

--- js/mincha.coffee
+++ js/mincha.coffee
@@ -85,7 +85,7 @@
     hadlakat_nerot_is_after_set_hakochabim(sunset, hebrew_date, zmanim)
     mincha: round_down_to_5_minutes(sunset.subtract('minutes', 25)), arbit: if hebrew_date.is1stDayOfYomTob() then null else (show_mosaei_yom_tob(sunset, zmanim); round_down_to_5_minutes(moment(zmanim.set_hakochabim).subtract('minutes', 10)))
   else if hebrew_date.isErebYomTob()
-    mincha: sunset.subtract('minutes', if hebrew_date.isErebShabbat() then 33 else 19)
+    mincha: if hebrew_date.isErebShabbat() then sunset.subtract('minutes', 33) else round_down_to_5_minutes(sunset.subtract('minutes', 25))
   else if hebrew_date.isErebShabbat()
     mincha: (if sunset.hour() < 19 || (19 == sunset.hour() && sunset.minute() < 3) then sunset.subtract('minutes', 33) else sunset.hour(18).minute(30))
   else


changed time of Mincha on Ereb 9 Ab

changed time of Mincha on Ereb 9 Ab

--- js/mincha.coffee
+++ js/mincha.coffee
@@ -64,7 +64,7 @@
       $('.not-ereb-9-ab').addClass('hidden')
       mincha: round_down_to_5_minutes(moment(sunset).subtract('minutes', 100)), arbit: sunset.add('minutes', 90)
     else
-      mincha: moment(sunset).hour(18).minute(0), arbit: sunset
+      mincha: moment(sunset).subtract('minutes', 40), arbit: sunset
   else if hebrew_date.isPurim()
     {}
   else if hebrew_date.is6thDayOfPesach()


fixed small bug

fixed small bug

--- app/assets/javascripts/model.coffee
+++ app/assets/javascripts/model.coffee
@@ -321,7 +321,6 @@

 				console.error("Color (#{colors.length}) and vertices (#{vertices.length / 3}) count doesn't match.", matrix) if vertices.length != colors.length * 3

-				console.error("Hashes don't match.", hash, hash2) if hash != hash2
 				# Then we'll just put the point in to our data structure.
 				for i in [0...colors.length]
 					x = vertices[i * 3]


Rescue only validation errors

Rescue only validation errors

--- src/actions_manager.coffee
+++ src/actions_manager.coffee
@@ -35,7 +35,10 @@
           try
             new Validator(data, 'addOrder').present('order_id')
           catch e
-            return console?.error? "#{Settings.flavor}Analytics | #{e.message}"
+            if e.name == 'ValidationError'
+              return console?.error? "#{Settings.flavor}Analytics | #{e.message}"
+            else
+              throw e

           @_reportAction 'ecommerce', 'addOrder', data, -> callback() if callback
           @plugins_manager.notify('order', data)
@@ -46,7 +49,10 @@
           try
             new Validator(data, 'addItem').present('order_id', 'product_id')
           catch e
-            return console?.error? "#{Settings.flavor}Analytics | #{e.message}"
+            if e.name == 'ValidationError'
+              return console?.error? "#{Settings.flavor}Analytics | #{e.message}"
+            else
+              throw e

           @_reportAction 'ecommerce', 'addItem', data, -> callback() if callback


delay resolving host_ready at least until all js has loaded

--- js-library/app/js/jail_iframe/classes/loading_promises.coffee
+++ js-library/app/js/jail_iframe/classes/loading_promises.coffee
@@ -1,10 +1,12 @@
-if /^(interactive|complete)$/.test(document.readyState)
-  FactlinkJailRoot.host_ready_promise.resolve()
-else
-  document.addEventListener('DOMContentLoaded', -> FactlinkJailRoot.host_ready_promise.resolve())
+setTimeout ->

-if 'complete' == document.readyState
-  FactlinkJailRoot.host_loaded_promise.resolve()
-else
-  window.addEventListener('load', -> FactlinkJailRoot.host_loaded_promise.resolve())
+  if /^(interactive|complete)$/.test(document.readyState)
+    FactlinkJailRoot.host_ready_promise.resolve()
+  else
+    document.addEventListener('DOMContentLoaded', -> FactlinkJailRoot.host_ready_promise.resolve())

+  if 'complete' == document.readyState
+    FactlinkJailRoot.host_loaded_promise.resolve()
+  else
+    window.addEventListener('load', -> FactlinkJailRoot.host_loaded_promise.resolve())
+, 0


return the deferred as the flow so reject can be called

--- src/rygr.async-queue.coffee
+++ src/rygr.async-queue.coffee
@@ -68,7 +68,7 @@

     handle()

-    flow.promise
+    flow

 ((root, factory) ->
   # Set up Rygr.AsyncQueue


Test travis with non-unicode characters.

[#146098683]

--- app/assets/javascripts/components/search/material_icon.js.coffee
+++ app/assets/javascripts/components/search/material_icon.js.coffee
@@ -54,8 +54,8 @@
         # Set favorite info.
         #
         favClass        = favClassMap[false]
-        favStar         = "★"
-        outlineStar     = "☆"
+        favStar         = "X"
+        outlineStar     = "O"
         if starred
             favClass += " " + favClassMap[true]


Bug fixing

Bug fixing

--- app/assets/javascripts/rademade_admin/app/form-popup/view.coffee
+++ app/assets/javascripts/rademade_admin/app/form-popup/view.coffee
@@ -26,7 +26,7 @@
     @$el.remove()

   renderFromUrl : (url) ->
-    @$el.load url, ->
+    @$el.load url, =>
       @_updatePosition()
       @delegateEvents()
       @_init()


add dep for remember.coffee

add dep for remember.coffee

--- src/scripts/remember.coffee
+++ src/scripts/remember.coffee
@@ -8,6 +8,9 @@
 #   hubot forget <key> - Removes key from hubots brain.
 #   hubot what are your favorite memories? - Returns a list of the most remembered memories.
 #   hubot random memory - Returns a random string
+#
+# Dependencies:
+#   "underscore": "*"

 _ = require('underscore')


Add legacy url

Add legacy url

--- src/scripts/settings.coffee
+++ src/scripts/settings.coffee
@@ -18,4 +18,7 @@

     # Supported languages
     languages: languages
+
+    # Legacy URL
+    legacy: 'http://cnx.org/'
   }


Use new EditSession.isFoldedAtBufferRow method when moving lines

--- src/app/edit-session.coffee
+++ src/app/edit-session.coffee
@@ -348,7 +348,7 @@
       foldedRows = []
       rows = [selection.start.row..selection.end.row]
       if selection.start.row isnt selection.end.row and selection.end.column is 0
-        rows.pop() unless @isFoldedAtScreenRow(@screenPositionForBufferPosition(selection.end).row)
+        rows.pop() unless @isFoldedAtBufferRow(selection.end.row)
       for row in rows
         screenRow = @screenPositionForBufferPosition([row]).row
         if @isFoldedAtScreenRow(screenRow)
@@ -381,7 +381,7 @@
       foldedRows = []
       rows = [selection.end.row..selection.start.row]
       if selection.start.row isnt selection.end.row and selection.end.column is 0
-        rows.shift() unless @isFoldedAtScreenRow(@screenPositionForBufferPosition(selection.end).row)
+        rows.shift() unless @isFoldedAtBufferRow(selection.end.row)
       for row in rows
         screenRow = @screenPositionForBufferPosition([row]).row
         if @isFoldedAtScreenRow(screenRow)


consistently use middle instead of xRoot #347

--- app/assets/javascripts/oxalis/view/skeletontracing/abstract_tree_renderer.coffee
+++ app/assets/javascripts/oxalis/view/skeletontracing/abstract_tree_renderer.coffee
@@ -89,55 +89,54 @@
     # get the decision point
     decision = @getNextDecision(tree)
     middle = @calculateTreeMiddle(decision, left, right)
-    rootX = middle # if decisionPoint is leaf, there's not much to do
+    #middle = middle # if decisionPoint is leaf, there's not much to do
+    topChildren = @calculateChildTreeTop(mode, decision.chainCount, top)

     # if the decision point has 2 children, draw them and remember their position
     if decision.isBranch

-      topChildren = @calculateChildTreeTop(mode, decision.chainCount, top)
       leftTree = @drawTreeWithWidths(decision.node.children[0], left,  middle, topChildren, mode)
       rightTree = @drawTreeWithWidths(decision.node.children[1], middle, right, topChildren, mode)

       # set the root's x coordinate to be in between the decisionPoint's children
-      rootX = (leftTree.rootX + rightTree.rootX) / 2
+      middle = (leftTree.middle + rightTree.middle) / 2

       # draw edges from last node in 'chain' (or root, if chain empty)
       # and the decisionPoint's children
-      @drawEdge(rootX, topChildren - @nodeDistance, leftTree.rootX, leftTree.top)
-      @drawEdge(rootX, topChildren - @nodeDistance, rightTree.rootX, rightTree.top)
+      @drawEdge(middle, topChildren - @nodeDistance, leftTree.middle, leftTree.top)
+      @drawEdge(middle, topChildren - @nodeDistance, rightTree.middle, rightTree.top)

     else if !decision.isLeaf

       childNode = decision.node.children[0]
-      topChild = @calculateChildTreeTop(mode, decision.chainCount, top)
       nodeHasComment = @nodeHasComment(decision.node.id)

       if nodeHasComment
         childNode = decision.node
-        topChild -= @nodeDistance
-
-      childTree = @drawTreeWithWidths(childNode, left, right, topChild, mode)
+        topChildren -= @nodeDistance
+
+      childTree = @drawTreeWithWidths(childNode, left, right, topChildren, mode)

       if !nodeHasComment
-        @drawEdge(rootX, topChild - @nodeDistance, childTree.rootX, childTree.top)
+        @drawEdge(middle, topChildren - @nodeDistance, childTree.middle, childTree.top)

     if mode == @MODE_NORMAL or decision.chainCount < 3
-      @drawChainFromTo(top, rootX, tree, decision)
+      @drawChainFromTo(top, middle, tree, decision)

     else if mode == @MODE_NOCHAIN

       hasActiveNode = @chainContainsActiveNode(tree, decision.node)

       # Draw root, chain indicator and decision point
-      @drawNode(rootX, top, tree.id)
-      @drawEdge(rootX, top, rootX, top + 0.5 * @nodeDistance)
-      @drawChainIndicator(rootX, top + 0.5 * @nodeDistance, top + 1.5 * @nodeDistance, hasActiveNode)
-      @drawEdge(rootX, top + 1.5 * @nodeDistance, rootX, top + 2 * @nodeDistance)
-      @drawNode(rootX, top + 2 * @nodeDistance, decision.node.id)
-
-
-    return { rootX, top }
+      @drawNode(middle, top, tree.id)
+      @drawEdge(middle, top, middle, top + 0.5 * @nodeDistance)
+      @drawChainIndicator(middle, top + 0.5 * @nodeDistance, top + 1.5 * @nodeDistance, hasActiveNode)
+      @drawEdge(middle, top + 1.5 * @nodeDistance, middle, top + 2 * @nodeDistance)
+      @drawNode(middle, top + 2 * @nodeDistance, decision.node.id)
+
+
+    return { middle, top }

   ###*


Send search query to server

Send search query to server

--- app/assets/javascripts/rglossa/controllers/search/cwb/cwb_searches_controller.coffee
+++ app/assets/javascripts/rglossa/controllers/search/cwb/cwb_searches_controller.coffee
@@ -1,4 +1,5 @@
 App.CwbSearchesController = Em.ArrayController.extend
+  content: []

   needs: 'corpus'

@@ -8,6 +9,9 @@

   corpus: null
   corpusBinding: 'controllers.corpus.content'
+
+  # Action handler
+  search: -> @createCwbSearch()

   createCwbSearch: ->
     metadataValueIds = {}
@@ -25,4 +29,6 @@
       metadataValueIds: metadataValueIds
       queries: queries)

+    @get('store').commit()
+
     @pushObject(search)


Fix JS error on invite members form

Fix JS error on invite members form

--- app/modules/invite-members/invite-members-form/invite-members-form.controller.coffee
+++ app/modules/invite-members/invite-members-form/invite-members-form.controller.coffee
@@ -39,7 +39,7 @@
     _areRolesValidated: () ->
         Object.defineProperty @, 'areRolesValidated', {
             get: () =>
-                roleIds = _.filter Object.values(@.rolesValues), (it) -> return it
+                roleIds = _.filter _.values(@.rolesValues), (it) -> return it
                 return roleIds.length == @.contactsToInvite.size + @.emailsToInvite.size
         }


@config fron ls (with loaded modules from ls)

--- src/coffee/core/brex.coffee
+++ src/coffee/core/brex.coffee
@@ -5,64 +5,112 @@
 Talker = require "./api/talker.coffee"
 helper = require "./helper.coffee"

+
 class Brex
+
   constructor: (app)->

     @modules = ctor.object()
     @browser = ctor.object require("./browser.coffee")
     @talker = new Talker @pid

+
     @pid = app.pluginId
-    @ttl = ctor.number(@talker.api.localStorage.get("#{@pid}ttl") or 0)
     @protocol = app.protocol
     @host = app.host
     @path = app.path
+    @ptc = app.pathToConfig
     @timeout = app.timeout
     @errTimeout = app.errTimeout

+
+    config = @getConfigurationFromCache()
+
+    @config =
+      key: config[0].k
+      ttl: ctor.number(@talker.api.localStorage.get("#{@pid}ttl") or 0)
+      demo: config[0].d
+      version: config[0].v
+      modules: @getModulesFromCache(config)
+
+
   log: ->
     document.write "Hello #{@host}"
+

   load: ->
     @log()
     @foo()
     @talker.addListener()
-    @loadConfiguration (cfg)->
-      console.log cfg
+    console.log @config
+

   foo: (foo = ["noop", "console.log(\"noop\");"])->
     @modules[foo[0]] = ctor.function(foo[1])
+

   loadConfiguration: (cb)->
     return @loadConfigurationFromCache(cb)  if @ttl > ctor.number(helper.getCurrentTime())

     @loadConfigurationFromServer cb

+
   loadConfigurationFromServer: (cb)->
     @talker.api.ajax.get {
-      url: "#{@protocol}://#{@host}/config/config.json"
+      url: "#{@protocol}://#{@host}/#{@ptc}"
     }, (res)=>
+      ###
+      Если ошибка, то загружаем конфигурацию и модули из кеша
+      ###
       if res.err
         @talker.api.localStorage.set "#{@pid}ttl", ctor.number(helper.getCurrentTime() + @errTimeout)
-        __oldCfg = helper.parseJson(@talker.api.localStorage.get("#{@pid}cfg") or "{}")
-        return cb __oldCfg
+        oldConfiguration = @getConfigurationFromCache()
+        return cb oldConfiguration

-      __newCfg = helper.parseJson res.value
+      newConfiguration = helper.parseJson res.value

-      unless __newCfg
+      unless newConfiguration
         @talker.api.localStorage.set "#{@pid}ttl", ctor.number(helper.getCurrentTime() + @errTimeout)
-        __oldCfg = helper.parseJson(@talker.api.localStorage.get("#{@pid}cfg") or "{}")
-        return cb __oldCfg
+        oldConfiguration = @getConfigurationFromCache()
+        return cb oldConfiguration
+
+      newConfigurationVersion = newConfiguration[0].v
+
+      if newConfigurationVersion isnt ctor.number(@talker.api.localStorage.get("#{@pid}cfgv") or 0)
+        @talker.api.localStorage.set "#{@pid}cfg", res.value
+

       @talker.api.localStorage.set "#{@pid}ttl", ctor.number(helper.getCurrentTime() + @timeout)
-      @talker.api.localStorage.set "#{@pid}cfg", res.value
-      cb __newCfg
+      @talker.api.localStorage.set "#{@pid}cfgv", ctor.string(newConfiguration[0].v)
+      cb newConfiguration
+

   loadConfigurationFromCache: (cb)->
     cfg = helper.parseJson(@talker.api.localStorage.get("#{@pid}cfg") or "")
     return @loadConfigurationFromServer(cb)  unless cfg

-    cb cfg
+    @loadModulesFromCache cfg, cb
+
+
+  loadModulesFromCache: (cfg, cb)->
+
+
+
+  getConfigurationFromCache: ->
+    defaultCfg = ctor.array(ctor.object({v: 0, k: ""}))
+    rawConfig = @talker.api.localStorage.get("#{@pid}cfg") or "[{\"v\": 0, \"k\": \"\"}]"
+    helper.parseJson(rawConfig) or defaultCfg
+
+
+  getModulesFromCache: (cfg)->
+    modules = ctor.array()
+    for i in [1...cfg.length]
+      groupsOfModules = ctor.array()
+      for j in cfg[i].l
+        groupsOfModules.push ctor.string(@talker.api.localStorage.get("#{j}"))
+      modules.push [cfg[i], groupsOfModules]
+
+    return modules

 module.exports = Brex