home/stacks: use compute storage for stack template item

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

--- client/home/lib/stacks/components/stacktemplateitem/index.coffee
+++ client/home/lib/stacks/components/stacktemplateitem/index.coffee
@@ -25,23 +25,23 @@

   componentWillReceiveProps: (nextProps) ->

-    nextStackUnreadCount = @getStackUnreadCount nextProps.stack
-    @setState { showWidget : yes }  if nextStackUnreadCount > @getStackUnreadCount()
+    if @props.stack?.getUnreadCount() < nextProps.stack?.getUnreadCount()
+      @setState { showWidget: yes }

     @setCoordinates()

   componentDidMount: ->

-    $('.kdscrollview').on 'scroll', @bound 'scrollOnPage'
+    $('.kdscrollview').on 'scroll', @bound 'onScrollPage'
     @setCoordinates()

   componentWillUnmount: ->
-    $('.kdscrollview').off 'scroll', @bound 'scrollOnPage'
+    $('.kdscrollview').off 'scroll', @bound 'onScrollPage'

-  scrollOnPage: -> _.debounce =>
+  onScrollPage: -> _.debounce =>
     @setState({ showWidget: no })
   , 500, { leading: yes, trailing: no }

@@ -56,38 +56,29 @@

   renderButton: ->

-    { onAddToSidebar, onRemoveFromSidebar, isVisibleOnSidebar, onCloneFromDashboard, template } = @props
+    { onAddToSidebar, onRemoveFromSidebar,
+      isVisibleOnSidebar, onCloneFromDashboard, template } = @props

-    if onCloneFromDashboard and template.get('originId') isnt whoami()._id
-      CLONE STACK
+    if onCloneFromDashboard and not template.isMine()
+      <ItemLink
+        onClick={onCloneFromDashboard}
+        title='CLONE STACK' />
+
+    else if isVisibleOnSidebar
+      <ItemLink
+        onClick={onRemoveFromSidebar}
+        title='REMOVE FROM SIDEBAR' />
     else
-
-      if isVisibleOnSidebar
-        REMOVE FROM SIDEBAR
-      else
-        ADD TO SIDEBAR
-
-
-  renderCloneButton: ->
-
-    { template, onCloneFromDashboard } = @props
-    return  if template.get('accessLevel') is 'private'
-    return  if template.get('originId') is whoami()._id
-
-
-
-  getStackUnreadCount: (stack = @props.stack) ->
-
-    stack?.getIn [ '_revisionStatus', 'status', 'code' ]
+      <ItemLink
+        onClick={onAddToSidebar}
+        title='ADD TO SIDEBAR' />

   renderUnreadCount: ->

-    return null  unless @getStackUnreadCount()
+    return null  unless count = @props.stack?.getUnreadCount()

-    <UnreadCount
-      count={@getStackUnreadCount()}
-      onClick={@bound 'handleUnreadCountClick'} />
+    <UnreadCount count={count} onClick={@bound 'handleUnreadCountClick'} />

   handleUnreadCountClick: ->
@@ -105,7 +96,7 @@

     { coordinates, showWidget } = @state

-    return null  unless @getStackUnreadCount()
+    return null  unless @props.stack?.getUnreadCount()
     return null  if not coordinates.left and coordinates.top

     coordinates.top = coordinates.top - 160
@@ -119,30 +110,33 @@
       onClose={@bound 'onWidgetClose'}
     />

+
   renderDisabledStack: (stack, onOpen) ->

-        { makeTitle { stack } }
-
+        onClick={onOpen}
+        children={makeTitle({ stack })} />
+

-        Last updated <TimeAgo from={stack.getIn ['meta', 'modifiedAt']} />
+        Last Updated <TimeAgo from={stack.meta.modifiedAt}

+

         {@renderButton()}

+
   renderTags: ->

     { template, onCloneFromDashboard } = @props

-    if isDefaultTeamStack template.get '_id'
+    if isDefaultTeamStack template.getId()

DEFAULT

-    else if onCloneFromDashboard and template.get('accessLevel') is 'group'
+    else if onCloneFromDashboard and template.accessLevel is 'group'

SHARED

     else
       null
@@ -152,10 +146,13 @@

     { template, stack, onOpen } = @props

-    return @renderDisabledStack(stack, onOpen)  if stack?.get 'disabled'
-    return null  unless template
+    if stack?.disabled
+      return renderDisabledStack stack, onOpen

-    editorUrl = "/Stack-Editor/#{template.get '_id'}"
+    if not template
+      return null
+
+    editorUrl = "/Stack-Editor/#{template.getId()}"

@@ -163,15 +160,15 @@
           ref='stackTemplateItem'
           href={editorUrl}
           className='HomeAppViewListItem-label'
-          onClick={onOpen}>
-          { makeTitle { template, stack } }
+          onClick={onOpen}
+          children={ makeTitle { template, stack } }>

         {@renderUnreadCount()}
         {@renderTags()}

       {@renderStackUpdatedWidget()}

-        Last updated <TimeAgo from={template.getIn ['meta', 'modifiedAt']} />
+        Last updated <TimeAgo from={template.meta.modifiedAt} />

         {@renderButton()}
@@ -181,10 +178,19 @@

 makeTitle = ({ template, stack }) ->

-  title = _.unescape template?.get 'title'
+  title = _.unescape template?.title

   return title  unless stack
-  if stack.getIn(['config', 'oldOwner'])
+
+  if stack.getOldOwner()
     title = stack.get 'title'

   return "#{title}"
+
+
+ItemLink = ({ onClick, title }) ->
+


restore missing an app default link - fixes #3

--- src/services/theming/theming.svc.coffee
+++ src/services/theming/theming.svc.coffee
@@ -46,7 +46,8 @@
       dataNotFoundConfig:
         mainMessage: 'Data not found'
         linkMessage: 'Are you missing an app?'
-        linkUrl: ''
+        linkUrl: '/apps'
+        linkTarget: '_blank'
       # options for the widget selector panel.
       widgetSelectorConfig:
         path: null


ts and tsx support...

ts and tsx support...

--- src/smc-webapp/file-associations.coffee
+++ src/smc-webapp/file-associations.coffee
@@ -74,6 +74,7 @@
     txt    : 'text'
     tex    : 'stex2'
     ts     : 'application/typescript'
+    tsx    : 'text/typescript-jsx'
     toml   : 'text/x-toml'
     bib    : 'stex'
     bbl    : 'stex'
@@ -97,6 +98,10 @@
             'fab fa-js-square'
         when 'jsx'
             'fab fa-node-js'
+        when 'application/typescript' # it would be nice to have proper TS icons...
+            'fab fa-js-square'
+        when 'text/typescript-jsx'    # would be nice to have proper TS...
+            'fab fa-node-js'
         else
             'fa-file-code-o'
     if ext in ['r', 'rmd']


Reuse testhelper for first import tests.

--- src/spec/integration/import.spec.coffee
+++ src/spec/integration/import.spec.coffee
@@ -2,6 +2,7 @@
 _ = require 'underscore'
 Import = require '../../lib/import'
 Config = require '../../config'
+TestHelpers = require './testhelpers'

 jasmine.getEnv().defaultTimeoutInterval = 30000

@@ -13,7 +14,7 @@
 describe 'Import', ->
   beforeEach (done) ->
     @importer = createImporter()
-    @rest = @importer.validator.rest
+    @client = @importer.client

     values = [
       { key: 'x', label: 'X' }
@@ -40,51 +41,7 @@
         { name: 'multiSamelEnum', label: { de: 'multiSamelEnum' }, type: { name: 'set', elementType: { name: 'lenum', values: lvalues } }, attributeConstraint: 'SameForAll', isRequired: false, isSearchable: false }
       ]

-    deleteProduct = (product) =>
-      deferred = Q.defer()
-      data =
-        id: product.id
-        version: product.version
-        actions: [
-          action: 'unpublish'
-        ]
-      @rest.POST "/products/#{product.id}", JSON.stringify(data), (error, response, body) =>
-        if response.statusCode is 200
-          product.version = body.version
-        @rest.DELETE "/products/#{product.id}?version=#{product.version}", (error, response, body) ->
-          deferred.resolve response.statusCode
-      deferred.promise
-
-    deleteProductType = (productType) =>
-      deferred = Q.defer()
-      @rest.DELETE "/product-types/#{productType.id}?version=#{productType.version}", (error, response, body) ->
-        deferred.resolve response.statusCode
-      deferred.promise
-
-    @rest.GET '/products?staged=true', (error, response, body) =>
-      expect(response.statusCode).toBe 200
-      productDeletes = []
-      typesDeletes = []
-      for product in body.results
-        productDeletes.push deleteProduct(product)
-      @rest.GET '/product-types?limit=0', (error, response, body) =>
-        expect(response.statusCode).toBe 200
-        for productType in body.results
-          typesDeletes.push deleteProductType(productType)
-        Q.all(productDeletes).then (statusCodes) =>
-          Q.all(typesDeletes).then (statusCodes) =>
-            @rest.POST '/product-types', JSON.stringify(@productType), (error, response, body) =>
-              expect(response.statusCode).toBe 201
-              @productType = body
-              channel =
-                key: 'retailerA'
-                roles: [ 'InventorySupply' ]
-              @rest.POST '/channels', JSON.stringify(channel), (error, response, body) ->
-                done()
-          .fail (msg) ->
-            expect(true).toBe false
-        .fail (msg) ->
-          expect(true).toBe false
+    TestHelpers.setup @client, @productType, undefined, done

   describe '#import', ->
     it 'should import a simple product', (done) ->
@@ -96,8 +53,9 @@
       @importer.import csv, (res) ->
         expect(res.status).toBe true
         expect(res.message).toBe '[row 2] New product created.'
-        done()
-
+      done()
+
+  xdescribe '#import', ->
     it 'should import a product with prices', (done) ->
       csv =
         """


Workspace: Fixed editor pane files option.

--- client/landing/app/CommonViews/workspace/panes/editorpane.coffee
+++ client/landing/app/CommonViews/workspace/panes/editorpane.coffee
@@ -6,7 +6,7 @@

     super options, data

-    @files = @getOptions "files"
+    {@files} = @getOptions()

     if Array.isArray @files then @createEditorTabs() else @createSingleEditor()


Button: Added click related test to button.

--- src/tests/components/test_button.coffee
+++ src/tests/components/test_button.coffee
@@ -15,7 +15,7 @@

   beforeEach ->
     isClicked = null
-    options   = title: 'Spark Button'
+    options   = title: 'Spark Button', callback: -> isClicked = yes
     button    = new spark.components.Button options
     element   = button.getElement()

@@ -30,6 +30,21 @@

     it 'should be a button element and has span as a child', ->
       expect(element.tagName).toBe 'BUTTON'
+
+
+  it 'should call callback when clicked', ->
+    flag = no
+
+    expect(isClicked).toBeNull()
+
+    fireClick()
+
+    expect(isClicked).toBeTruthy()
+
+    button.on   'click', -> flag = !flag
+    button.emit 'click'
+
+    expect(flag).toBeTruthy()

   describe 'disable', ->


better menus with separators

better menus with separators

--- src/ui/views/menu.coffee
+++ src/ui/views/menu.coffee
@@ -46,14 +46,6 @@
             accelerator: 'Command+Control+F',
             click: -> action 'togglefullscreen'
         }, {
-            label: 'Previous Conversation',
-            enabled: viewstate.loggedin
-            click: -> action 'selectNextConv', -1
-        }, {
-            label: 'Next Conversation',
-            enabled: viewstate.loggedin
-            click: -> action 'selectNextConv', +1
-        }, {
             # seee https://github.com/atom/electron/issues/1507
             label: 'Zoom In',
             accelerator: 'Command+Plus',
@@ -67,6 +59,18 @@
             accelerator: 'Command+0',
             click: -> action 'zoom'
         }, {
+            type: 'separator'
+        }, {
+            label: 'Previous Conversation',
+            enabled: viewstate.loggedin
+            click: -> action 'selectNextConv', -1
+        }, {
+            label: 'Next Conversation',
+            enabled: viewstate.loggedin
+            click: -> action 'selectNextConv', +1
+        }, {
+            type: 'separator'
+        }, {
             label: 'Show tray icon'
             type: 'checkbox'
             enabled: not viewstate.hidedockicon
@@ -77,7 +81,7 @@
             type: 'checkbox'
             enabled: viewstate.showtray
             checked:  viewstate.hidedockicon
-            click: -> action 'toggledockicon'
+            click: -> action 'togglehidedockicon'
         }
     ]},{
     label: 'Window',
@@ -130,16 +134,6 @@
             accelerator: 'Control+Alt+F',
             click: -> action 'togglefullscreen'
         }, {
-            label: 'Previous Conversation',
-            accelerator: 'Control+K',
-            click: -> action 'selectNextConv', -1
-            enabled: viewstate.loggedin
-        }, {
-            label: 'Next Conversation',
-            accelerator: 'Control+J',
-            click: -> action 'selectNextConv', +1
-            enabled: viewstate.loggedin
-        }, {
             # seee https://github.com/atom/electron/issues/1507
             label: 'Zoom In',
             accelerator: 'Control+Plus',
@@ -152,6 +146,26 @@
             label: 'Reset Zoom',
             accelerator: 'Control+0',
             click: -> action 'zoom'
+        }, {
+          type: 'separator'
+        }, {
+            label: 'Previous Conversation',
+            accelerator: 'Control+K',
+            click: -> action 'selectNextConv', -1
+            enabled: viewstate.loggedin
+        }, {
+            label: 'Next Conversation',
+            accelerator: 'Control+J',
+            click: -> action 'selectNextConv', +1
+            enabled: viewstate.loggedin
+        }, {
+          type: 'separator'
+        }, {
+            label: 'Show tray icon'
+            type: 'checkbox'
+            enabled: not viewstate.hidedockicon
+            checked:  viewstate.showtray
+            click: -> action 'toggleshowtray'
         }
     ]}
 ]


Make "original-fs" available as built-in module

--- atom/common/lib/asar_init.coffee
+++ atom/common/lib/asar_init.coffee
@@ -9,10 +9,10 @@

   # Make graceful-fs work with asar.
   source = process.binding 'natives'
-  source.originalFs = source.fs
-  source.fs = """
+  source['original-fs'] = source.fs
+  source['fs'] = """
     var src = '(function (exports, require, module, __filename, __dirname) { ' +
-              process.binding('natives').originalFs +
+              process.binding('natives')['original-fs'] +
               ' });';
     var vm = require('vm');
     var fn = vm.runInThisContext(src, { filename: 'fs.js' });


When undoing we don't need to check if the opinion is actually correct

--- core/app/backbone/views/fact_relations/argument_vote_view.coffee
+++ core/app/backbone/views/fact_relations/argument_vote_view.coffee
@@ -8,9 +8,9 @@

   events:
     'click .js-believe':      -> @model.believe()
-    'click .js-unbelieve':    -> @unset_opinion 'believe'
+    'click .js-unbelieve':    -> @model.undoOpinion()
     'click .js-disbelieve':   -> @model.disbelieve()
-    'click .js-undisbelieve': -> @unset_opinion 'disbelieve'
+    'click .js-undisbelieve': -> @model.undoOpinion()

   templateHelpers: =>
     believes: @model.isUserOpinion 'believe'
@@ -21,20 +21,15 @@
       @render()
       highlight @$el

-  unset_opinion: (opinion) ->
-    return unless @model.isUserOpinion opinion
-
-    @model.undoOpinion()
-
 class FactRelationVoteLineView extends Backbone.Marionette.ItemView

   className: 'vote-up-down-line'

   events:
     'click .js-believe':      -> @model.believe()
-    'click .js-unbelieve':    -> @unset_opinion 'believes'
+    'click .js-unbelieve':    -> @model.undoOpinion()
     'click .js-disbelieve':   -> @model.disbelieve()
-    'click .js-undisbelieve': -> @unset_opinion 'disbelieves'
+    'click .js-undisbelieve': -> @model.undoOpinion()

   templateHelpers: =>
     believes: @model.isBelieving()
@@ -44,11 +39,6 @@
     @listenTo @model, "change:current_user_opinion", ->
       @render()
       highlight @$el
-
-  unset_opinion: (opinion) ->
-    return unless @model.current_opinion() == opinion
-
-    @model.undoOpinion()

 class ArgumentVoteView extends Backbone.Marionette.Layout
   className: 'vote-up-down'


Fixes middleware tests

Fixes middleware tests

--- apps/partner2/test/routes.coffee
+++ apps/partner2/test/routes.coffee
@@ -18,7 +18,7 @@
     Backbone.sync.restore()

   describe '#requireNewLayout', ->
-    _.each ['gallery_one', 'gallery_two', 'gallery_three'], (layout) ->
+    _.each ['gallery_one', 'gallery_two', 'gallery_three', 'institution'], (layout) ->
       it "nexts to the middleware in this route stack if the profile layout is #{layout}", ->
         partnerProfile = new Profile fabricate 'partner_profile',
           owner: fabricate 'partner', profile_layout: layout
@@ -27,8 +27,6 @@
         @next.calledOnce.should.be.ok
         _.isUndefined(@next.args[0][0]).should.be.ok()

-    # TODO Update this test when institutions are approved
-    # _.each ['institution', 'gellery_default', 'gallery_deprecated'], (layout) ->
     _.each ['gellery_default', 'gallery_deprecated'], (layout) ->
       it "skips the middlewares from this route stack if the profile layout is #{layout}", ->
         deprecatedLayoutPartnerProfile = new Profile fabricate 'partner_profile',


Truncate title for blocks and channels

Truncate title for blocks and channels

--- apps/user/queries/profile.coffee
+++ apps/user/queries/profile.coffee
@@ -1,13 +1,14 @@
 module.exports =
   """
-  query user($id: ID! $per: Int, $page: Int, $perBlocks: Int, $q: String) {
+  query user($id: ID! $per: Int, $page: Int, $perBlocks: Int, $q: String,  $sort: SearchSorts, $seed: Int) {
     user(id: $id) {
-      contents(per: $per, type: "channel", page: $page, q: $q) {
-        title
+      contents(per: $per, type: "channel", page: $page, q: $q, sort_by: $sort, seed: $seed) {
+        title(truncate: 50)
         updated_at(relative: true)
         user {
           name
         }
+        id
         kind {
           __typename
           ... on Channel {


Spine.model.host back to localhost

Spine.model.host back to localhost

--- app/index.coffee
+++ app/index.coffee
@@ -16,7 +16,7 @@
 UserEdit = require('controllers/user_edit')
 Notifications = require('controllers/notifications')

-Spine.Model.host = if document.location.href.match(/localhost|192./) then "http://192.168.1.199:3000/api/v1" else "http://www.boorgle.com/api/v1"
+Spine.Model.host = if document.location.href.match(/localhost|192./) then "http://localhost:3000/api/v1" else "http://www.boorgle.com/api/v1"

 class App extends Stage.Global
   Authorization: Authorization


Added TopFactEvidenceLayoutView and some dummy data.

Conflicts:
	app/backbone/views/evidence/top_fact_evidence_view.coffee

--- core/app/backbone/views/evidence/top_fact_evidence_view.coffee
+++ core/app/backbone/views/evidence/top_fact_evidence_view.coffee
@@ -1,2 +1,18 @@
+class TopFactEvidenceLayoutView extends Backbone.Marionette.Layout
+  template:
+    text: """
+        Blah
+      """
+
 class window.TopFactEvidenceView extends Backbone.Marionette.CollectionView
-  template: 'facts/top_fact_arguments'
+  className: -> 'top-fact-arguments'
+
+  itemView: TopFactEvidenceLayoutView
+
+  constructor: ->
+    @collection = new Backbone.Collection [
+      new Backbone.Model(impact: 30, opinion_type: 'believe')
+      new Backbone.Model(impact: 20, opinion_type: 'doubt')
+      new Backbone.Model(impact: 10, opinion_type: 'disbelieve')
+    ]
+    super


[benchmarks] fix for garbage.

[benchmarks] fix for garbage.

--- benchmarks/garbage/garbage.coffee
+++ benchmarks/garbage/garbage.coffee
@@ -45,6 +45,7 @@
         Framework.spawnClientsInProcess
             numClients: Opts.numBrowsers
             sharedBrowser: false
+            serverAddress: 'http://localhost:3000'
             clientCallback: (client, cb) ->
                 console.log("[iteration #{i}] created client: #{client.id}")
                 cb()


Rename a poorly named variable

Rename a poorly named variable

--- content_scripts/link_hints.coffee
+++ content_scripts/link_hints.coffee
@@ -149,8 +149,8 @@
           map = document.querySelector "map[name=\"#{mapName}\"]"
           if map and imgClientRects.length > 0
             areas = map.getElementsByTagName "area"
-            areaRects = DomUtils.getClientRectsForAreas imgClientRects[0], areas
-            visibleElements = visibleElements.concat areaRects
+            areasAndRects = DomUtils.getClientRectsForAreas imgClientRects[0], areas
+            visibleElements = visibleElements.concat areasAndRects

       # Check aria properties to see if the element should be ignored.
       if (element.getAttribute("aria-hidden")?.toLowerCase() in ["", "true"] or


Query filterLinkHints setting every time, not just page load

--- content_scripts/link_hints.coffee
+++ content_scripts/link_hints.coffee
@@ -24,7 +24,8 @@
   delayMode: false
   # Handle the link hinting marker generation and matching. Must be initialized after settings have been
   # loaded, so that we can retrieve the option setting.
-  markerMatcher: undefined
+  markerMatcher: ->
+    if settings.get("filterLinkHints") then filterHints else alphabetHints
   # lock to ensure only one instance runs at a time
   isActive: false

@@ -32,7 +33,6 @@
   # To be called after linkHints has been generated from linkHintsBase.
   #
   init: ->
-    @markerMatcher = if settings.get("filterLinkHints") then filterHints else alphabetHints

   #
   # Generate an XPath describing what a clickable element is.
@@ -60,7 +60,7 @@
     @isActive = true

     @setOpenLinkMode(mode)
-    hintMarkers = @markerMatcher.fillInMarkers(@createMarkerFor(el) for el in @getVisibleClickableElements())
+    hintMarkers = @markerMatcher().fillInMarkers(@createMarkerFor(el) for el in @getVisibleClickableElements())

     # Note(philc): Append these markers as top level children instead of as child nodes to the link itself,
     # because some clickable elements cannot contain children, e.g. submit buttons. This has the caveat
@@ -161,7 +161,7 @@
     visibleElements

   #
-  # Handles shift and esc keys. The other keys are passed to markerMatcher.matchHintsByKey.
+  # Handles shift and esc keys. The other keys are passed to markerMatcher().matchHintsByKey.
   #
   onKeyDownInMode: (hintMarkers, event) ->
     return if @delayMode
@@ -183,7 +183,7 @@
     if (KeyboardUtils.isEscape(event))
       @deactivateMode()
     else if (event.keyCode != keyCodes.shiftKey)
-      keyResult = @markerMatcher.matchHintsByKey(hintMarkers, event)
+      keyResult = @markerMatcher().matchHintsByKey(hintMarkers, event)
       linksMatched = keyResult.linksMatched
       delay = keyResult.delay ? 0
       if (linksMatched.length == 0)
@@ -194,7 +194,7 @@
         for marker in hintMarkers
           @hideMarker(marker)
         for matched in linksMatched
-          @showMarker(matched, @markerMatcher.hintKeystrokeQueue.length)
+          @showMarker(matched, @markerMatcher().hintKeystrokeQueue.length)
     false # We've handled this key, so prevent propagation.

   #
@@ -239,8 +239,8 @@
   #
   deactivateMode: (delay, callback) ->
     deactivate = =>
-      if (LinkHints.markerMatcher.deactivate)
-        LinkHints.markerMatcher.deactivate()
+      if (LinkHints.markerMatcher().deactivate)
+        LinkHints.markerMatcher().deactivate()
       if (LinkHints.hintMarkerContainingDiv)
         DomUtils.removeElement LinkHints.hintMarkerContainingDiv
       LinkHints.hintMarkerContainingDiv = null


fixed unconnected stackeds

fixed unconnected stackeds

--- src/viz/types/helpers/graph/nest.coffee
+++ src/viz/types/helpers/graph/nest.coffee
@@ -24,10 +24,10 @@
   d3.nest()
     .key (d) ->
       return_id = "nesting"
-      for id in vars.id.nesting
+      for id in vars.id.nesting.slice 0, vars.depth.value+1
         val = fetchValue vars, d, id
         val = val.join("_") if val instanceof Array
-        return_id += "_"+val
+        return_id += "_"+stringStrip val
       return_id
     .rollup (leaves) ->


Real debug

Real debug

--- test/mocha.coffee
+++ test/mocha.coffee
@@ -16,4 +16,4 @@
 global.sinon = require("sinon")

 Promise = require "bluebird"
-Promise.longStackTraces()
+process.env.BLUEBIRD_DEBUG=1


clean up code

clean up code

--- app/controllers/classifier.coffee
+++ app/controllers/classifier.coffee
@@ -64,7 +64,6 @@
       location.hash = "#/verify"

     window.classifier = @
-
     @recordedClickEvents = [] # array to store all click events

     # zoom levels [days]: 2x, 10x, 20x
@@ -76,14 +75,13 @@

     # classification counts at which to display supplementary tutorial
     @whenToDisplayTips = [1, 7] # TODO: don't forget to add 4 after beta version
+    @splitDesignation = null

     User.on 'change', @onUserChange
     Subject.on 'fetch', @onSubjectFetch
     Subject.on 'select', @onSubjectSelect
     @Subject = Subject

-    @splitDesignation = null
-
     $(document).on 'mark-change', => @updateButtons()
     @marksContainer = @el.find('#marks-container')[0]

@@ -97,12 +95,9 @@

     # mini course
     @course = new MiniCourse
-
     @el.find('#course-interval-setter').hide()

     # @verifyRate = 20
-
-    @recordedClickEvents = []

     @el.find('#no-transits').hide() #prop('disabled',true)
     @el.find('#finished-marking').hide() #prop('disabled',true)
@@ -127,11 +122,11 @@
   # /////////////////////////////////////////////////

   onChangeSupplementalOption: ->
-    console.log 'onChangeSupplementalOption(): '
+    # console.log 'onChangeSupplementalOption(): '
     return unless User.current?

   onChangeMiniCourseOption: ->
-    console.log 'onChangeMiniCourseOption(): '
+    # console.log 'onChangeMiniCourseOption(): '
     return unless User.current?
     courseOption = User.current.preferences.planet_hunter.course

@@ -150,22 +145,22 @@
     User.current?.setPreference 'course', courseOption

   onChangeCourseInterval: ->
-    console.log 'VALUE: ', @el.find('#course-interval').val()
+    # console.log 'VALUE: ', @el.find('#course-interval').val()
     defaultValue = 5
     value = +@el.find('#course-interval').val()

-    console.log 'VALUE IS NUMBER: ', (typeof value)
+    # console.log 'VALUE IS NUMBER: ', (typeof value)

     # validate integer values
     unless (typeof value is 'number') and (value % 1 is 0) and value > 0 and value < 100
       value = defaultValue
       @el.find('#course-interval').val(value)
     else
-      console.log 'SETTING VALUE TO: ', value
+      # console.log 'SETTING VALUE TO: ', value
       @course.setRate value

   onChangeCourseIntervalViaSupTut: ->
-    console.log 'onChangeCourseIntervalViaSupTut(): '
+    # console.log 'onChangeCourseIntervalViaSupTut(): '
     defaultValue = 5
     value = +@el.find('#course-interval-sup-tut').val()

@@ -174,7 +169,7 @@
       value = defaultValue
       @el.find('#course-interval-sup-tut').val(value)
     else
-      console.log 'SETTING VALUE TO: ', value
+      # console.log 'SETTING VALUE TO: ', value

     @course.setRate value

@@ -186,9 +181,7 @@
     @recordedClickEvents.push clickEvent

   onUserChange: (e, user) =>
-    console.log 'classify: onUserChange()'
-
-    # console.log 'SPLIT DESIGNATION: ', User.current.project.splits.mini_course_sup_tutorial
+    # console.log 'classify: onUserChange()'
     if User.current?
       @handleSplitDesignation()
       if User.current.preferences?.planet_hunter?
@@ -198,50 +191,46 @@
             @courseOptionIsChecked = false
           else if @splitDesignation in ['d', 'e', 'f', 'j', 'k', 'l']
             @courseOptionIsChecked = true
-
       if @courseOptionIsChecked
         User.current?.setPreference 'course', 'yes'
       else
         User.current?.setPreference 'course', 'no'
-
+
     # handle first-time users
     if +preferences?.count? is 0 or not User.current?
-      console.log 'First-time user. Loading tutorial...'
-      # @onClickTutorial()
       @launchTutorial()

     Subject.next() unless @classification?

   handleSplitDesignation: ->
-    console.log 'handleSplitDesignation()'
     @splitDesignation = User.current.project.splits.mini_course_sup_tutorial
     @splitDesignation = 'd' # DEBUG CODE

     # HANDLE MINI-COURSE SPLITS
     if @splitDesignation in ['b', 'e']
-      console.log 'Setting mini-course interval to 10'
+      # console.log 'Setting mini-course interval to 10'
       @course.setRate 10
       $('#course-interval-setter').remove() # destroy custom course interval setter

     else if @splitDesignation in ['c', 'f']
-      console.log 'Setting mini-course interval to 25'
+      # console.log 'Setting mini-course interval to 25'
       @course.setRate 25
       $('#course-interval-setter').remove() # destroy custom course interval setter

     else if @splitDesignation in ['a', 'd']
-      console.log 'Setting mini-course interval to 5'
+      # console.log 'Setting mini-course interval to 5'
       @course.setRate 5 # set default
       @allowCustomCourseInterval = true
     else
-      console.log 'Setting mini-course interval to 5'
+      # console.log 'Setting mini-course interval to 5'
       @allowCustomCourseInterval = false
       @course.setRate 5 # set default

   onSubjectFetch: (e, user) =>
-    console.log 'onSubjectFetch(): '
+    # console.log 'onSubjectFetch(): '

   onSubjectSelect: (e, subject) =>
-    console.log 'onSubjectSelect(): '
+    # console.log 'onSubjectSelect(): '
     @subject = subject
     @classification = new Classification {subject}
     @loadSubjectData()
@@ -319,7 +308,7 @@
     # console.log '-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-'

   onClickZoom: ->
-    console.log 'onClickZoom()'
+    # console.log 'onClickZoom()'

     # # DO WE NEED THIS?
     # @prevZoomLevel = @zoomLevel
@@ -348,7 +337,7 @@

       # zoom in to new range
       @canvasGraph.zoomInTo(val, val+@zoomRanges[@zoomLevel])
-      console.log 'zoomInTo(', val, ',', val+@zoomRanges[@zoomLevel], ')'
+      # console.log 'zoomInTo(', val, ',', val+@zoomRanges[@zoomLevel], ')'

       # rebuild slider
@@ -460,7 +449,6 @@
         teff: "4056"
       selected_light_curve:
         location: 'https://s3.amazonaws.com/demo.zooniverse.org/planet_hunter/beta_subjects/1873513_15-3.json'
-    console.log 'TUTORIAL SUBJECT: ', tutorialSubject
     tutorialSubject

   updateButtons: ->


Add more code to validation.coffee

Add more code to validation.coffee

--- app/controllers/validation.coffee
+++ app/controllers/validation.coffee
@@ -1,13 +1,37 @@
 definitions = {}

+getDef = (name) ->
+  def = definitons[nmea]
+  if not def then throw new Error 'Could not find', name
+  return def
+
 checkType = (obj, type) ->
-    return typeof obj is type
+  return typeof obj is type

 define = (name, type, details) ->
-  def = definitions[name]
-  if def then throw new Error('Definition already defined: ' + name)
-  def = (obj) ->
+  if definitions[name]
+    throw new Error('Definition already defined: ' + name)
+
+  if not details
+    fn = (obj) -> checkType(obj, type)
+
+  inherit = details.inherit
+  if typeof inherit is 'function'
+    inherit = (obj) ->
+      return definitions[details.inherit(obj)](obj)
+  else if typeof inherit is 'string'
+    inherit = definitions[inherit]
+
+  keys = {}
+  if details.keys
+    for key, type of details.keys
+      keys[key] = getDef(type)
+
+  definitions[name] = (obj) ->
     return false unless checkType obj, type
+
+    if inherit then return false unless inherit(obj)
+
     return true

 defineFn = (name, args...) ->
@@ -15,6 +39,21 @@
     for arg, i in input
       return false unless checkType arg, args[i]
     return true
+
+
+# ----------------------------------------------------------------------------
+# Native
+# ----------------------------------------------------------------------------
+
+define 'function', 'function'
+define 'string', 'string'
+define 'number', 'number'
+define 'boolean', 'boolean'
+
+define 'bool', 'boolean'
+define 'int', 'number'
+define 'fn', 'function'
+

 # ----------------------------------------------------------------------------
 # Models


Fixed #1008

Fixed #1008

--- app/lib/surface/CocoSprite.coffee
+++ app/lib/surface/CocoSprite.coffee
@@ -109,6 +109,7 @@
   destroy: ->
     mark.destroy() for name, mark of @marks
     label.destroy() for name, label of @labels
+    p.removeChild @healthBar if p = @healthBar?.parent
     @imageObject?.off 'animationend', @playNextAction
     clearInterval @effectInterval if @effectInterval
     super()


fixed rport test

fixed rport test

--- test/rport.coffee
+++ test/rport.coffee
@@ -3,7 +3,7 @@
 assert = require('assert')
 util = require('util')

-sip.start rport: true, (rq) -> sip.send sip.makeResponse rq, 500
+# sip.start rport: true, (rq) -> sip.send sip.makeResponse rq, 500

 socket = udp.createSocket 'udp4'

@@ -25,8 +25,11 @@
     rs = sip.stringify sip.makeResponse (sip.parse msg), 200
     socket.send (new Buffer rs), 0, rs.length, rinfo.port, rinfo.address

+  sip.start rport: true, (rq) -> sip.send sip.makeResponse rq, 500
+
   sip.send sip.copyMessage(message), (rs) ->
     assert.equal 200, rs.status
+    sip.stop()
     success()

 test2 = (success) ->
@@ -34,14 +37,16 @@
     parsed = sip.parse msg
     rs = sip.stringify sip.makeResponse parsed, 200
     socket.send (new Buffer rs), 0, rs.length, parsed.headers.via[0].port, rinfo.address
+
+  sip.start rport: true, (rq) -> sip.send sip.makeResponse rq, 500

   sip.send message,
     (rs) ->
       assert.equal 200, rs.status
+      sip.stop()
       success()

 test3 = (success) ->
-  sip.stop()
   sip.start rport: false, (rq) -> sip.send sip.makeResponse rq, 500

   socket.once 'message', (msg, rinfo) ->
@@ -50,7 +55,9 @@
     rs = sip.stringify sip.makeResponse (sip.parse msg), 200
     socket.send (new Buffer rs), 0, rs.length, rinfo.port, rinfo.address

-  sip.send sip.copyMessage(message), () -> success()
+  sip.send sip.copyMessage(message), () ->
+    sip.stop()
+    success()

 exports.tests = [test1, test2, test3]


Tweak

Tweak

--- test/suite.coffee
+++ test/suite.coffee
@@ -5,4 +5,4 @@
 queue.defer (callback) -> require('./unit/cache')({}, callback) # TODO
 queue.defer (callback) -> require('./unit/fabricator')({}, callback)
 queue.defer (callback) -> require('./unit/all_generators')({}, callback)
-queue.await (err) -> console.log "ORM: Completed tests"
+queue.await (err) -> console.log "Backbone ORM: Completed tests"


Give focus to title input field when editing

--- client/editProject.coffee
+++ client/editProject.coffee
@@ -8,7 +8,6 @@
   editor.attachAce()
   if text
     editor.setValue(text, 0)
-  editor.setFocus()
   editor.ace.on("change", ->
     logger.debug("Project text has changed - setting dirty state")
     Session.set("isProjectModified", true)
@@ -17,6 +16,9 @@
   editor.ace.gotoLine(0, 0)
   editor.ace.session.setUseWrapMode(true)

+Template.editProject.rendered = ->
+  logger.debug("Project editing view rendered")
+  document.getElementById("title-input").focus()
 Template.descriptionEditor.rendered = ->
   logger.debug("Description editor rendered, giving Ace focus")
   handleEditorRendered(descriptionEditor, @data.description)


tests use in-memory Robot.Brain

tests use in-memory Robot.Brain

--- test/tests.coffee
+++ test/tests.coffee
@@ -31,7 +31,7 @@

 class Helper extends Robot
   constructor: (path) ->
-    super path, 'helper'
+    super path, 'helper', Robot.Brain
     @sent = []
     @Response = Helper.Response


fix typo

fix typo

--- test/spec-e2e/lib/hello-world-spec.coffee
+++ test/spec-e2e/lib/hello-world-spec.coffee
@@ -3,7 +3,7 @@

   sharedExamplesFor "a hello world", ->
     When -> @el = browser.findElementByCssSelector('.hello')
-    Then -> "Hello, World!" @el.getText()
+    Then -> "Hello, World!" == @el.getText()
     And -> @el.getCssValue('backgroundColor') == "rgb(239, 239, 239)"

   describe "lineman build", ->


Fix templateCache inject

Fix templateCache inject

--- src/src.coffee
+++ src/src.coffee
@@ -33,7 +33,7 @@

     $templateCache.put "views/#{viewPathCleanHtml}", views(viewPath)()

-templateCache.$nject = ['$templateCache']
+templateCache.$inject = ['$templateCache']

 angular.module('ap-file-upload').run templateCache


feat(config.cson): update some properties

--- atom/config.cson
+++ atom/config.cson
@@ -33,15 +33,35 @@
     fontSize: 15
     invisibles: {}
     nonWordCharacters: "/\\()\"':,.;<>~!@#$%^&*|+=[]{}`?…"
-    preferredLineLength: 120
+    preferredLineLength: 100
     showIndentGuide: true
     softWrap: true
     softWrapAtPreferredLineLength: true
   "exception-reporting":
     userId: "884440fb-9e84-fcf9-8239-ecee423c921e"
-  "linter-eslint":
-    fixOnSave: true
+  "file-types":
+    hbs: "text.html.htmlbars"
+  "linter-eslint": {}
   "linter-ui-default":
     panelHeight: 81
+  "prettier-atom":
+    formatOnSaveOptions:
+      enabled: true
+      showInStatusBar: true
+    prettierOptions:
+      arrowParens: "always"
+    useEslint: true
   welcome:
     showOnStartup: false
+".css.source.styled":
+  editor:
+    nonWordCharacters: "/\\()\"':,.;<>~!#$@%^&*|+=[]{}`?…"
+".handlebars.html.text":
+  editor:
+    nonWordCharacters: "/\\()\"':,.;<>~!@#$%^&*|+=[]{}`?…"
+".html.htmlbars.text":
+  editor:
+    nonWordCharacters: "/\\()\"':,.;<>~!@#$%^&*|+=[]{}`?…"
+".js.jsx.source":
+  editor:
+    nonWordCharacters: "/\\()\"':,.;<>~!@#%^&*|+=[]{}`?…"


Disable background-tips, exception reporting and metrics plugins, cleanup

--- atom/config.cson
+++ atom/config.cson
@@ -9,29 +9,22 @@
     disabledPackages: [
       "autocomplete"
       "spell-check"
+      "background-tips"
+      "exception-reporting"
+      "metrics"
     ]
     projectHome: "/home/edgard/Documents/code"
     themes: [
       "one-dark-ui"
       "one-dark-syntax"
     ]
-  "exception-reporting":
-    userId: "1b98cd61-c072-c275-56fd-5715ea36d0bd"
   welcome:
     showOnStartup: false
-  "autocomplete-plus": {}
-  minimap: {}
   "git-diff":
     showIconsInEditorGutter: true
-  "spell-check": {}
-  "atom-terminal":
-    app: "/usr/bin/gnome-terminal"
-  linter: {}
   "atom-beautify":
-    _analyticsUserId: "a6a0720b-ef9b-4635-a6bf-67af0c531216"
+    analytics: false
   "go-plus":
     goPath: "/Users/edgard/Documents/code/go"
   tabs:
     enableVcsColoring: true
-  "status-bar": {}
-  "linter-elixirc": {}


Add Atom keymap for triggering smalls package

See package at https://atom.io/packages/smalls.

--- atom/keymap.cson
+++ atom/keymap.cson
@@ -21,6 +21,8 @@
   'ctrl-. z': 'dot-atom:scroll-cursor-to-center'
   'ctrl-. b': 'dot-atom:scroll-cursor-to-bottom'

+  'ctrl-/': 'smalls:start'
+
   'ctrl-d': 'toggle-markdown-task:toggle'

 # The maximize-panes package (https://atom.io/packages/maximize-panes)


avstreams.create() makes an id if it is not given

--- avstreams.coffee
+++ avstreams.coffee
@@ -1,6 +1,19 @@
+crypto = require 'crypto'
+
 h264 = require './h264'
 EventEmitterModule = require './event_emitter'
 logger = require './logger'
+
+createStreamId = ->
+  try
+    buf = crypto.randomBytes 256
+  catch e
+    logger.error "crypto.randomBytes() failed: #{e}"
+    buf = crypto.pseudoRandomBytes 256
+
+  shasum = crypto.createHash 'sha512'
+  shasum.update buf
+  return shasum.digest 'hex'

 class AVStream
   constructor: (id) ->
@@ -177,7 +190,21 @@
   get: (streamId) ->
     return streams[streamId]

+  createNewStreamId: ->
+    retryCount = 0
+    loop
+      id = createStreamId()
+      if not api.exists id
+        return id
+      retryCount++
+      if retryCount >= 100
+        throw new Error "avstreams.createNewStreamId: Too many retries"
+
+  # Creates a new stream.
+  # If streamId is not given, a unique id will be generated.
   create: (streamId) ->
+    if not streamId?
+      streamId = api.createNewStreamId()
     stream = new AVStream streamId
     api.emit 'new', stream
     api.add stream


use util.qw() in codemirror-apl-mode.coffee

--- client/codemirror-apl-mode.coffee
+++ client/codemirror-apl-mode.coffee
@@ -1,15 +1,16 @@
 CodeMirror = require 'codemirror'
+{qw} = require './util'

 rLetter = @rLetter = 'A-Z_a-zÀ-ÖØ-Ýß-öø-üþ∆⍙Ⓐ-Ⓩ'; rName0 = ///[#{rLetter}]///; rName1 = ///[#{rLetter}\d]*///
 rNotName = ///[^#{rLetter}\d]+///
-keywords = '''
+keywords = qw '''
   andif access case caselist class continue else elseif end endclass endfor
   endhold endif endinterface endnamespace endproperty endrepeat endsection
   endselect endtrap endwhile endwith field for in goto hold include if
   implements interface leave namespace orif property repeat return section
   select trap until while with
-'''.split /[ \r\n]+/
-quadNames = [''].concat '''
+'''
+quadNames = [''].concat qw '''
   á a af ai an arbin arbout arg at av avu base class clear cmd cr cs ct cy d
   dct df div dl dm dmx dq dr ea ec ed em en env es et ex exception export
   fappend favail fc fchk fcopy fcreate fdrop ferase fhold fix flib fmt fnames
@@ -21,7 +22,7 @@
   size sm sr src stack state stop svc sve svo svq svr svs syl tc tcnums tf tget
   this tid tkill tname tnums tpool tput treq trace trap ts tsync tz ucs ul
   using vfi vr wa wc wg wn ws wsid wx x xml xsi xt
-'''.split /[ \r\n]+/
+'''

 CodeMirror.defineMIME 'text/apl', 'apl'
 CodeMirror.defineMode 'apl', -> # https://codemirror.net/doc/manual.html#modeapi


Revert "Disable extending of single-line comments in Atom"

This reverts commit bfa0ffc8e1fda55f1c7f482d59fb99d60f075e57.

--- atom/config.cson
+++ atom/config.cson
@@ -84,7 +84,6 @@
     titleBar: "custom"
   docblockr:
     align_tags: "shallow"
-    extend_double_slash: false
   editor:
     autoIndentOnPaste: false
     fontFamily: "Monaco"


Remove java-specific wrap guide, add 120

--- atom/config.cson
+++ atom/config.cson
@@ -30,13 +30,10 @@
     columns: [
       80
       100
+      120
     ]
     locked: true
 ".java.source":
-  "multi-wrap-guide":
-    columns: [
-      100
-    ]
   editor:
     preferredLineLength: 100
     tabLength: 4


default to nospawn

default to nospawn

--- config/application.coffee
+++ config/application.coffee
@@ -172,6 +172,9 @@
       port: 3000

   watch:
+    options:
+      spawn: false
+
     js:
       files: ["<%= files.js.vendor %>", "<%= files.js.app %>"]
       tasks: ["concat:js"]


dont show the confirm registration popup on feature pages

--- apps/feature/client/router.coffee
+++ apps/feature/client/router.coffee
@@ -19,13 +19,13 @@
     @currentUser.fetch success: success

   confirmBid: ->
-    @fetchUser =>
-      new ConfirmBidModal feature: @feature, paddleNumber: @currentUser.get('paddle_number')
-      mediator.on 'modal:closed', => Backbone.history.navigate(@feature.href(), trigger: true, replace: true)
-      analytics.track.click "Showed 'Confirm bid' on feature page"
+    # @fetchUser =>
+    #   new ConfirmBidModal feature: @feature, paddleNumber: @currentUser.get('paddle_number')
+    #   mediator.on 'modal:closed', => Backbone.history.navigate(@feature.href(), trigger: true, replace: true)
+    #   analytics.track.click "Showed 'Confirm bid' on feature page"

   confirmRegistration: ->
-    @fetchUser =>
-      new ConfirmRegistrationModal feature: @feature, paddleNumber: @currentUser.get('paddle_number')
-      mediator.on 'modal:closed', => Backbone.history.navigate(@feature.href(), trigger: true, replace: true)
-      analytics.track.click "Showed 'Confirm registration' on feature page"
+    # @fetchUser =>
+    #   new ConfirmRegistrationModal feature: @feature, paddleNumber: @currentUser.get('paddle_number')
+    #   mediator.on 'modal:closed', => Backbone.history.navigate(@feature.href(), trigger: true, replace: true)
+    #   analytics.track.click "Showed 'Confirm registration' on feature page"


Add option to enable case sensitive search

Fixes #2509

--- coffee/lib/abstract-chosen.coffee
+++ coffee/lib/abstract-chosen.coffee
@@ -33,6 +33,7 @@
     @display_disabled_options = if @options.display_disabled_options? then @options.display_disabled_options else true
     @include_group_label_in_selected = @options.include_group_label_in_selected || false
     @max_shown_results = @options.max_shown_results || Number.POSITIVE_INFINITY
+    @case_sensitive_search = @options.case_sensitive_search || false

   set_default_text: ->
     if @form_field.getAttribute("data-placeholder")
@@ -202,7 +203,8 @@

   get_search_regex: (escaped_search_string) ->
     regex_anchor = if @search_contains then "" else "^"
-    new RegExp(regex_anchor + escaped_search_string, 'i')
+    regex_flag = if @case_sensitive_search then "" else "i"
+    new RegExp(regex_anchor + escaped_search_string, regex_flag)

   search_string_match: (search_string, regex) ->
     if regex.test search_string


Removes -swx70 canonical_name suffix for dalan oberos in the m12-l

--- coffeescripts/cards-common.coffee
+++ coffeescripts/cards-common.coffee
@@ -4939,7 +4939,7 @@
         }
         {
             name: 'Dalan Oberos (Kimogila)'
-            canonical_name: "#{'Dalan Oberos'.canonicalize()}-swx70"
+            canonical_name: "dalanoberos"
             id: 272
             unique: true
             faction: 'Scum and Villainy'


From BoLS unboxing video.

From BoLS unboxing video.

--- coffeescripts/cards-common.coffee
+++ coffeescripts/cards-common.coffee
@@ -2992,7 +2992,7 @@
             points: 37
         }
         {
-            name: 'Lothal ???'
+            name: 'Lothal Rebel'
             id: 158
             faction: "Rebel Alliance"
             ship: "VCX-100"
@@ -3005,7 +3005,7 @@
                 'Crew'
                 'Crew'
             ]
-            points: 100
+            points: 35
         }
         {
             name: 'Hera Syndulla (Attack Shuttle)'
@@ -3090,15 +3090,16 @@
             points: 22
         }
         {
-            name: "Baron of ???"
+            name: "Baron of the Empire"
             id: 165
             faction: "Galactic Empire"
             ship: "TIE Advanced Prototype"
             skill: 4
             slots: [
+                'Elite'
                 'Missile'
             ]
-            points: 100
+            points: 19
         }
         {
             name: "Sienar Test Pilot"


cleanup

cleanup

--- tutor/specs/components/book-link.spec.cjsx
+++ tutor/specs/components/book-link.spec.cjsx
@@ -2,17 +2,6 @@

 BookLink = require '../../src/components/qa/book-link'
 Context = require './helpers/enzyme-context'
-
-# {ExerciseActions, ExerciseStore} = require '../../src/flux/exercise'
-# {EcosystemsActions, EcosystemsStore} = require '../../src/flux/ecosystems'
-# {ReferenceBookActions, ReferenceBookStore} = require '../../src/flux/reference-book'
-
-# EXERCISES  = require '../../api/exercises.json'
-# ECOSYSTEMS = require '../../api/ecosystems.json'
-# PAGE = require '../../api/ecosystems/3/readings.json'
-# COURSE_ID = '1'
-# ECOSYSTEM_ID = '3'
-# CNX_ID = '17f6ff53-2d92-4669-acdd-9a958ea7fd0a@12'

 describe 'QA Exercises Book Link', ->
@@ -27,5 +16,4 @@

   it 'renders', ->
     wrapper = shallow(<BookLink {...@props} />, Context.build())
-    console.log wrapper.debug()
     undefined


fixed the no results display

fixed the no results display

--- scripts/sentiment.coffee
+++ scripts/sentiment.coffee
@@ -145,17 +145,17 @@

   robot.respond /who( i|')s happy\??/i, (msg) ->
     # responds in the current channel
-    msg.send "Top happy people:\n" + (prettyPrintList || "Nobody... :(\n") onlyPositive getTopForWeek('descending', 10, 'user', getWeekOfYear())
+    msg.send "Top happy people:\n" + prettyPrintList (onlyPositive || "Nobody... :(\n") getTopForWeek('descending', 10, 'user', getWeekOfYear())

   robot.respond /who( i|')s (sad|stress)\??/i, (msg) ->
     # responds in the current channel
-    msg.send "Top stressed people:\n" + (prettyPrintList || "Nobody! :)\n") onlyNegative getTopForWeek('ascending', 10, 'user', getWeekOfYear())
+    msg.send "Top stressed people:\n" + prettyPrintList (onlyNegative || "Nobody! :)\n") getTopForWeek('ascending', 10, 'user', getWeekOfYear())

   robot.respond /where( i|')s( the)? happiness\??/i, (msg) ->
     # responds in the current channel
-    msg.send "Top happy channels:\n" + (prettyPrintList || "People need more hugs.\n") onlyPositive getTopForWeek('descending', 10, 'channel', getWeekOfYear())
+    msg.send "Top happy channels:\n" + prettyPrintList (onlyPositive || "People need more hugs.\n") getTopForWeek('descending', 10, 'channel', getWeekOfYear())

   robot.respond /where( i|')s( the)? (sad|stress)\??/i, (msg) ->
     # responds in the current channel
-    msg.send "Top stressed channels:\n" +  (prettyPrintList || "Everything is dandy!\n") onlyNegative getTopForWeek('ascending', 10, 'channel', getWeekOfYear())
+    msg.send "Top stressed channels:\n" +  prettyPrintList (onlyNegative || "Everything is dandy!\n") getTopForWeek('ascending', 10, 'channel', getWeekOfYear())


Fixed group selection if no group is selected.

--- app/assets/javascripts/app/controllers/customer_ticket_new.js.coffee
+++ app/assets/javascripts/app/controllers/customer_ticket_new.js.coffee
@@ -94,6 +94,10 @@
       group_ids = App.Config.get('customer_ticket_create_group_ids')

       # return all groups if no one is selected
+      return collection if !group_ids
+      return collection if !_.isArray( group_ids ) && group_ids is ''
+      return collection if _.isEmpty( group_ids )
+
       if !_.isArray( group_ids )
          group_ids = [group_ids]


newpie experiments

newpie experiments

--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -192,7 +192,8 @@
             bbox = @getBBox()
             #bbox.width and bbox.height will
             #describe the size of the label text
-            labelRadius = radius - 20
+            #labelRadius = radius - 20
+            labelRadius = radiuso * 1.2
             d.x = Math.sin(a) * labelRadius
             d.l = d.x - bbox.width / 2 - 2
             d.r = d.x + bbox.width / 2 + 2


Agent pie: start/end angle pie transition work

--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -209,7 +209,7 @@
             console.log('1. in tween - window.oldPieData[' + myInstanceDataId + '][' + i + ']: ',window.oldPieData[myInstanceDataId][i])
             console.log('1. in tween - d: ',d)
             myInterpolater = d3.interpolate(window.oldPieData[myInstanceDataId][i] ? d, d)
-            window.oldPieData[myInstanceDataId][i] = myInterpolater(0) ? null
+            window.oldPieData[myInstanceDataId][i] = myInterpolater(0) ? d
             console.log('2. in tween - window.oldPieData[' + myInstanceDataId + '][' + i + ']: ',window.oldPieData[myInstanceDataId][i])
             console.log('2. in tween - d: ',d)
             return (t) ->


vm: koding vms should also have a group context

--- workers/social/lib/social/models/vm.coffee
+++ workers/social/lib/social/models/vm.coffee
@@ -238,7 +238,8 @@
             user
             target  : member
             sudo    : yes
-            name    : member.profile.nickname
+            name    : "koding~#{member.profile.nickname}"
+            groups  : wrapGroup group
           }
         else
           member.checkPermission group, 'sudoer', (err, hasPermission)->


Fixed a bug in payments js at backend

Fixed a bug in payments js at backend

fixes #6129

--- backend/app/assets/javascripts/spree/backend/payments/edit.js.coffee
+++ backend/app/assets/javascripts/spree/backend/payments/edit.js.coffee
@@ -3,7 +3,7 @@
   order_id = $('#payments').data('order-id')
   class Payment
     constructor: (id) ->
-      @url  = Spree.url("#{Spree.routes.payments_api(order_id)}/#{id}.json")
+      @url  = Spree.url("#{Spree.routes.payments_api(order_id)}/#{id}.json" + '?token=' + Spree.api_key)
       @json = $.getJSON @url.toString(), (data) =>
         @data = data


payments_api needs an argument

payments_api needs an argument

--- backend/app/assets/javascripts/spree/backend/payments/edit.js.coffee
+++ backend/app/assets/javascripts/spree/backend/payments/edit.js.coffee
@@ -3,7 +3,7 @@
   order_id = $('#payments').data('order-id')
   class Payment
     constructor: (id) ->
-      @url  = "#{Spree.routes.payments_api}/#{id}.json"
+      @url  = "#{Spree.routes.payments_api(order_id)}/#{id}.json"
       @json = Spree.getJSON @url, (data) =>
         @data = data


client: intercom: fixed/added copy

client: intercom: fixed/added copy

--- client/home/lib/utilities/components/intercomintegration/view.coffee
+++ client/home/lib/utilities/components/intercomintegration/view.coffee
@@ -7,15 +7,17 @@
   render: ->

-      Intercom Integration
-      Enable Intercom.io for live chat with your team
+      Intercom
+      Enable Intercom.io for live chat, feedback, support and more.

+        It's a customer communication platform with a suite of integrated
+        products for every team—including sales, marketing, product, and support.
         For this integration you need to create an
         account at intercom.io.

-        Once you get your Intercom APP ID
-        and paste below, we will complete the integration for you.
+        Once you get your Intercom APP ID and
+        paste below, we will complete the integration for you.

       <filedset>
         Intercom.io APP ID


Fixed click event on figure

Fixed click event on figure

--- lib/written/app/assets/javascripts/written/uploaders/aws.coffee
+++ lib/written/app/assets/javascripts/written/uploaders/aws.coffee
@@ -4,15 +4,15 @@

   initialize: (node, observer) =>
     img = node.querySelector('img')
+    container = node.querySelector('div')

     node.dataset.uploadable = true
-    node.addEventListener 'click', @open.bind(this, node), true
     node.addEventListener 'written:uploader:error', @error, true
     node.addEventListener 'written:uploader:uploading', @progress, true
     node.addEventListener 'written:uploader:completed', @uploaded, true
-
     node.addEventListener 'change', @input.bind(this, node, observer), true

+    container.addEventListener 'click', @open.bind(this, node), true
     image = Images.get(img.dataset.image)
     if image
       image.node = node


Update lib/coffee/AccessTokenEncryptor.coffee

Co-Authored-By: Jakob Ackermann <caa3e4f01d56b797520cbfed54bdab4c5e3b0ebb@outlook.com>

--- libraries/access-token-encryptor/lib/coffee/AccessTokenEncryptor.coffee
+++ libraries/access-token-encryptor/lib/coffee/AccessTokenEncryptor.coffee
@@ -29,7 +29,7 @@
 			salt = results[0]
 			iv = results[1]

-			keyFn cipherPassword, salt, 32, (err, key) =>
+			keyFn @cipherPassword, salt, 32, (err, key) =>
 				if err?
 					logger.err err:err, "error getting Fn key"
 					return callback(err)


Re-work where listeners are installed.

Re-work where listeners are installed.

--- foreground.coffee
+++ foreground.coffee
@@ -2,12 +2,11 @@
 class Interface
   debug:
     config: false
-    selector: true
+    selector: false
     select: false

   constructor: (@config, element = null) ->
     @element = null
-    @isEnabled = true

     @config.offset ?= 100
     @config.nativeJK ?= false
@@ -17,25 +16,16 @@
       for own key, value of @config
         Common.log "  #{key} #{value}" unless key == "name"

-    Scroller.init()
-    Common.installListener window, "keydown", @keyDownHandler = (event) => @onKeyDown event
-    Common.installListener window, "keyup", @KeyUpHandler = (event) => @onKeyUp event
-    Common.installListener window, "scroll", @scrollHandler = => @onScroll()
     chrome.runtime.sendMessage name: "icon", show: true if @config.selectors?
-
-    @selectElement element false if element
+    @selectElement element, false if element
     # Disabled.  It's not clear that the UX is better like this on not.
     # @onScroll focusDelay: 0

   deactivate: ->
     chrome.runtime.sendMessage name: "icon", show: false
     @clearSelection()
-    @isEnabled = false
-    window.removeEventListener "keydown", @keyDownHandler, true
-    window.removeEventListener "keyup", @keyUphandler, true
-    window.removeEventListener "scroll", @scrollHandler, true
-
-  onKeyDown: (event) ->
+
+  onKeydown: (event) ->
     # console.clear()
     CoreScroller.registerKeydown event
     return unless Common.isActive()
@@ -61,7 +51,7 @@
     event.stopImmediatePropagation()
     false

-  onKeyUp: (event) ->
+  onKeyup: (event) ->
     CoreScroller.registerKeyup event

   performUpDown: (action) ->
@@ -142,9 +132,9 @@
       "visible"

   selectElement: (element, shouldScroll = true) ->
-    Common.log "selectElement #{element?} #{shouldScroll} #{@isEnabled}" if @debug.select
+    Common.log "selectElement #{element?} #{shouldScroll}" if @debug.select
     Common.log "              #{element == @element} (expect false for a new element)" if @debug.select
-    if element and @isEnabled
+    if element
       @clearSelection()
       @element = document.activeElement = element

@@ -246,18 +236,12 @@
 Wrapper =
   interface: null

-  # handleKeyEvent: (event) ->
-  #   switch event.type
-  #     when "keydown"
-  #       @interface?.onKeyDown event
-  #     when "keyup"
-  #       @interface?.onKeyUp event
-
   init: ->
-    # handleKeyEvent = (event) => @handleKeyEvent event
-    # for name in [ "keydown", "keyup" ]
-    #   Common.installListener window, name, handleKeyEvent
-
+    Common.installListener window, "keydown", (event) => @interface?.onKeydown event
+    Common.installListener window, "keyup", (event) => @interface?.onKeyup event
+    Common.installListener window, "scroll", (event) => @interface?.onScroll event
+
+    Scroller.init()
     @launch()

     chrome.runtime.onMessage.addListener (request, sender, sendResponse) =>
@@ -265,15 +249,13 @@
       false

   launch: ->
+    element = @interface?.element
     @interface?.deactivate()
     @interface = null

     chrome.runtime.sendMessage { name: "config", url: document.location.toString() }, (config) =>
       Common.log "config:", config?.name ? "disabled"
-      @interface = new Interface config if config
-
-if document?.location?.toString()
-  Wrapper.init()
-else
-  Common.documentReady -> Wrapper.init()
-
+      @interface = new Interface(config, element) if config
+
+Common.documentReady -> Wrapper.init()
+


fix settings link for org dashboard page

--- js/inner/pages/org_dashboard.coffee
+++ js/inner/pages/org_dashboard.coffee
@@ -4,7 +4,7 @@
     super(properties)
     @crumbs = [new CI.inner.OrgCrumb(@username, {active: true})]

-    @settings_link = CI.paths.org_dashboard_path(@username)
+    @settings_link = CI.paths.org_settings_path(@username)

     @settings_text = 'Organization Settings'


Bucket and the URL should be two different component

--- lib/written/app/assets/javascripts/written/uploaders/aws.coffee
+++ lib/written/app/assets/javascripts/written/uploaders/aws.coffee
@@ -90,7 +90,7 @@

   upload: (name, file) =>
     image = {
-      url: "#{@settings.bucket}/#{name}"
+      url: "#{@settings.url}/#{name}"
       name: name
       status: 'uploading'
       progress: 0
@@ -106,7 +106,7 @@
     form.append('file', file, name)

-    image.xhr = @send(form, @settings.bucket)
+    image.xhr = @send(form, @settings.url)
     image.xhr.addEventListener('load', @update.bind(this, image))
     image.xhr.addEventListener('error', @failed.bind(this, image))


Switch to using scoped variable instead of variables on context

Changes to mocha mean that a new context is passed to each describe block
instead of it persisting between them.

This means that this test cannot be parallelised, however this was the case
beforehand (subsequent tests are dependent on earlier tests).

--- services/web/test/acceptance/coffee/ProjectStructureTests.coffee
+++ services/web/test/acceptance/coffee/ProjectStructureTests.coffee
@@ -16,6 +16,12 @@
 User = require "./helpers/User"

 describe "ProjectStructureChanges", ->
+	example_project_id = null
+	example_doc_id = null
+	example_file_id = null
+	example_folder_id_1 = null
+	example_folder_id_2 = null
+
 	before (done) ->
 		@owner = new User()
 		@owner.login done
@@ -25,11 +31,11 @@
 			MockDocUpdaterApi.clearProjectStructureUpdates()
 			@owner.createProject "example-project", {template: "example"}, (error, project_id) =>
 				throw error if error?
-				@example_project_id = project_id
+				example_project_id = project_id
 				done()

 		it "should version creating a doc", ->
-			updates = MockDocUpdaterApi.getProjectStructureUpdates(@example_project_id).docUpdates
+			updates = MockDocUpdaterApi.getProjectStructureUpdates(example_project_id).docUpdates
 			expect(updates.length).to.equal(2)
 			_.each updates, (update) =>
 				expect(update.userId).to.equal(@owner._id)
@@ -38,7 +44,7 @@
 			expect(_.where(updates, pathname: "/references.bib").length).to.equal 1

 		it "should version creating a file", ->
-			updates = MockDocUpdaterApi.getProjectStructureUpdates(@example_project_id).fileUpdates
+			updates = MockDocUpdaterApi.getProjectStructureUpdates(example_project_id).fileUpdates
 			expect(updates.length).to.equal(1)
 			update = updates[0]
 			expect(update.userId).to.equal(@owner._id)
@@ -48,8 +54,9 @@
 	describe "duplicating a project", ->
 		before (done) ->
 			MockDocUpdaterApi.clearProjectStructureUpdates()
-			@owner.request.post {
-				uri: "/Project/#{@example_project_id}/clone",
+			console.log(example_project_id)
+			@owner.request.post {
+				uri: "/Project/#{example_project_id}/clone",
 				json:
 					projectName: 'new.tex'
 			}, (error, res, body) =>
@@ -80,10 +87,10 @@
 		before (done) ->
 			MockDocUpdaterApi.clearProjectStructureUpdates()

-			ProjectGetter.getProject @example_project_id, (error, project) =>
+			ProjectGetter.getProject example_project_id, (error, project) =>
 				throw error if error?
 				@owner.request.post {
-					uri: "project/#{@example_project_id}/doc",
+					uri: "project/#{example_project_id}/doc",
 					json:
 						name: 'new.tex'
 						parent_folder_id: project.rootFolder[0]._id
@@ -91,11 +98,11 @@
 					throw error if error?
 					if res.statusCode < 200 || res.statusCode >= 300
 						throw new Error("failed to add doc #{res.statusCode}")
-					@example_doc_id = body._id
+					example_doc_id = body._id
 					done()

 		it "should version the doc added", ->
-			updates = MockDocUpdaterApi.getProjectStructureUpdates(@example_project_id).docUpdates
+			updates = MockDocUpdaterApi.getProjectStructureUpdates(example_project_id).docUpdates
 			expect(updates.length).to.equal(1)
 			update = updates[0]
 			expect(update.userId).to.equal(@owner._id)
@@ -137,7 +144,7 @@

 	describe "uploading a file", ->
 		before (done) ->
-			ProjectGetter.getProject @example_project_id, (error, project) =>
+			ProjectGetter.getProject example_project_id, (error, project) =>
 				throw error if error?
 				@root_folder_id = project.rootFolder[0]._id.toString()
 				done()
@@ -149,7 +156,7 @@
 			image_file = fs.createReadStream(Path.resolve(__dirname + '/../files/1pixel.png'))

 			req = @owner.request.post {
-				uri: "project/#{@example_project_id}/upload",
+				uri: "project/#{example_project_id}/upload",
 				qs:
 					folder_id: @root_folder_id
 				formData:
@@ -163,9 +170,9 @@
 				if res.statusCode < 200 || res.statusCode >= 300
 					throw new Error("failed to upload file #{res.statusCode}")

-				@example_file_id = JSON.parse(body).entity_id
-
-				updates = MockDocUpdaterApi.getProjectStructureUpdates(@example_project_id).fileUpdates
+				example_file_id = JSON.parse(body).entity_id
+
+				updates = MockDocUpdaterApi.getProjectStructureUpdates(example_project_id).fileUpdates
 				expect(updates.length).to.equal(1)
 				update = updates[0]
 				expect(update.userId).to.equal(@owner._id)
@@ -179,7 +186,7 @@
 			image_file = fs.createReadStream(Path.resolve(__dirname + '/../files/2pixel.png'))

 			req = @owner.request.post {
-				uri: "project/#{@example_project_id}/upload",
+				uri: "project/#{example_project_id}/upload",
 				qs:
 					folder_id: @root_folder_id
 				formData:
@@ -193,7 +200,7 @@
 				if res.statusCode < 200 || res.statusCode >= 300
 					throw new Error("failed to upload file #{res.statusCode}")

-				updates = MockDocUpdaterApi.getProjectStructureUpdates(@example_project_id).fileUpdates
+				updates = MockDocUpdaterApi.getProjectStructureUpdates(example_project_id).fileUpdates
 				expect(updates.length).to.equal(1)
 				update = updates[0]
 				expect(update.userId).to.equal(@owner._id)
@@ -205,12 +212,12 @@
 	describe "moving entities", ->
 		before (done) ->
 			@owner.request.post {
-				uri: "project/#{@example_project_id}/folder",
+				uri: "project/#{example_project_id}/folder",
 				formData:
 					name: 'foo'
 			}, (error, res, body) =>
 				throw error if error?
-				@example_folder_id_1 = JSON.parse(body)._id
+				example_folder_id_1 = JSON.parse(body)._id
 				done()

 		beforeEach () ->
@@ -218,15 +225,15 @@

 		it "should version moving a doc", (done) ->
 			@owner.request.post {
-				uri: "project/#{@example_project_id}/Doc/#{@example_doc_id}/move",
+				uri: "project/#{example_project_id}/Doc/#{example_doc_id}/move",
 				json:
-					folder_id: @example_folder_id_1
+					folder_id: example_folder_id_1
 			}, (error, res, body) =>
 				throw error if error?
 				if res.statusCode < 200 || res.statusCode >= 300
 					throw new Error("failed to move doc #{res.statusCode}")

-				updates = MockDocUpdaterApi.getProjectStructureUpdates(@example_project_id).docUpdates
+				updates = MockDocUpdaterApi.getProjectStructureUpdates(example_project_id).docUpdates
 				expect(updates.length).to.equal(1)
 				update = updates[0]
 				expect(update.userId).to.equal(@owner._id)
@@ -237,15 +244,15 @@

 		it "should version moving a file", (done) ->
 			@owner.request.post {
-				uri: "project/#{@example_project_id}/File/#{@example_file_id}/move",
+				uri: "project/#{example_project_id}/File/#{example_file_id}/move",
 				json:
-					folder_id: @example_folder_id_1
+					folder_id: example_folder_id_1
 			}, (error, res, body) =>
 				throw error if error?
 				if res.statusCode < 200 || res.statusCode >= 300
 					throw new Error("failed to move file #{res.statusCode}")

-				updates = MockDocUpdaterApi.getProjectStructureUpdates(@example_project_id).fileUpdates
+				updates = MockDocUpdaterApi.getProjectStructureUpdates(example_project_id).fileUpdates
 				expect(updates.length).to.equal(1)
 				update = updates[0]
 				expect(update.userId).to.equal(@owner._id)
@@ -256,30 +263,30 @@

 		it "should version moving a folder", (done) ->
 			@owner.request.post {
-				uri: "project/#{@example_project_id}/folder",
+				uri: "project/#{example_project_id}/folder",
 				formData:
 					name: 'bar'
 			}, (error, res, body) =>
 				throw error if error?
-				@example_folder_id_2 = JSON.parse(body)._id
+				example_folder_id_2 = JSON.parse(body)._id

 				@owner.request.post {
-					uri: "project/#{@example_project_id}/Folder/#{@example_folder_id_1}/move",
+					uri: "project/#{example_project_id}/Folder/#{example_folder_id_1}/move",
 					json:
-						folder_id: @example_folder_id_2
+						folder_id: example_folder_id_2
 				}, (error, res, body) =>
 					throw error if error?
 					if res.statusCode < 200 || res.statusCode >= 300
 						throw new Error("failed to move folder #{res.statusCode}")

-					updates = MockDocUpdaterApi.getProjectStructureUpdates(@example_project_id).docUpdates
+					updates = MockDocUpdaterApi.getProjectStructureUpdates(example_project_id).docUpdates
 					expect(updates.length).to.equal(1)
 					update = updates[0]
 					expect(update.userId).to.equal(@owner._id)
 					expect(update.pathname).to.equal("/foo/new.tex")
 					expect(update.newPathname).to.equal("/bar/foo/new.tex")

-					updates = MockDocUpdaterApi.getProjectStructureUpdates(@example_project_id).fileUpdates
+					updates = MockDocUpdaterApi.getProjectStructureUpdates(example_project_id).fileUpdates
 					expect(updates.length).to.equal(1)
 					update = updates[0]
 					expect(update.userId).to.equal(@owner._id)
@@ -294,7 +301,7 @@

 		it "should version renaming a doc", (done) ->
 			@owner.request.post {
-				uri: "project/#{@example_project_id}/Doc/#{@example_doc_id}/rename",
+				uri: "project/#{example_project_id}/Doc/#{example_doc_id}/rename",
 				json:
 					name: 'new_renamed.tex'
 			}, (error, res, body) =>
@@ -302,7 +309,7 @@
 				if res.statusCode < 200 || res.statusCode >= 300
 					throw new Error("failed to move doc #{res.statusCode}")

-				updates = MockDocUpdaterApi.getProjectStructureUpdates(@example_project_id).docUpdates
+				updates = MockDocUpdaterApi.getProjectStructureUpdates(example_project_id).docUpdates
 				expect(updates.length).to.equal(1)
 				update = updates[0]
 				expect(update.userId).to.equal(@owner._id)
@@ -313,7 +320,7 @@

 		it "should version renaming a file", (done) ->
 			@owner.request.post {
-				uri: "project/#{@example_project_id}/File/#{@example_file_id}/rename",
+				uri: "project/#{example_project_id}/File/#{example_file_id}/rename",
 				json:
 					name: '1pixel_renamed.png'
 			}, (error, res, body) =>
@@ -321,7 +328,7 @@
 				if res.statusCode < 200 || res.statusCode >= 300
 					throw new Error("failed to move file #{res.statusCode}")

-				updates = MockDocUpdaterApi.getProjectStructureUpdates(@example_project_id).fileUpdates
+				updates = MockDocUpdaterApi.getProjectStructureUpdates(example_project_id).fileUpdates
 				expect(updates.length).to.equal(1)
 				update = updates[0]
 				expect(update.userId).to.equal(@owner._id)
@@ -332,7 +339,7 @@

 		it "should version renaming a folder", (done) ->
 			@owner.request.post {
-				uri: "project/#{@example_project_id}/Folder/#{@example_folder_id_1}/rename",
+				uri: "project/#{example_project_id}/Folder/#{example_folder_id_1}/rename",
 				json:
 					name: 'foo_renamed'
 			}, (error, res, body) =>
@@ -340,14 +347,14 @@
 				if res.statusCode < 200 || res.statusCode >= 300
 					throw new Error("failed to move folder #{res.statusCode}")

-				updates = MockDocUpdaterApi.getProjectStructureUpdates(@example_project_id).docUpdates
+				updates = MockDocUpdaterApi.getProjectStructureUpdates(example_project_id).docUpdates
 				expect(updates.length).to.equal(1)
 				update = updates[0]
 				expect(update.userId).to.equal(@owner._id)
 				expect(update.pathname).to.equal("/bar/foo/new_renamed.tex")
 				expect(update.newPathname).to.equal("/bar/foo_renamed/new_renamed.tex")

-				updates = MockDocUpdaterApi.getProjectStructureUpdates(@example_project_id).fileUpdates
+				updates = MockDocUpdaterApi.getProjectStructureUpdates(example_project_id).fileUpdates
 				expect(updates.length).to.equal(1)
 				update = updates[0]
 				expect(update.userId).to.equal(@owner._id)
@@ -362,20 +369,20 @@

 		it "should version deleting a folder", (done) ->
 			@owner.request.delete {
-				uri: "project/#{@example_project_id}/Folder/#{@example_folder_id_2}",
+				uri: "project/#{example_project_id}/Folder/#{example_folder_id_2}",
 			}, (error, res, body) =>
 				throw error if error?
 				if res.statusCode < 200 || res.statusCode >= 300
 					throw new Error("failed to delete folder #{res.statusCode}")

-				updates = MockDocUpdaterApi.getProjectStructureUpdates(@example_project_id).docUpdates
+				updates = MockDocUpdaterApi.getProjectStructureUpdates(example_project_id).docUpdates
 				expect(updates.length).to.equal(1)
 				update = updates[0]
 				expect(update.userId).to.equal(@owner._id)
 				expect(update.pathname).to.equal("/bar/foo_renamed/new_renamed.tex")
 				expect(update.newPathname).to.equal("")

-				updates = MockDocUpdaterApi.getProjectStructureUpdates(@example_project_id).fileUpdates
+				updates = MockDocUpdaterApi.getProjectStructureUpdates(example_project_id).fileUpdates
 				expect(updates.length).to.equal(1)
 				update = updates[0]
 				expect(update.userId).to.equal(@owner._id)


#22 clean metadata helper module.

#22 clean metadata helper module.

--- lib/helpers/metadata_builder.coffee
+++ lib/helpers/metadata_builder.coffee
@@ -1,102 +1,63 @@
 "use strict"
-((NS) ->
-  # Filename: helpers/metadata_builder.coffee
-  NS = NS or {}
-  isInBrowser = typeof module is 'undefined' and typeof window isnt 'undefined'
-  # Exceptions
-  ArgumentNullException = if isInBrowser then NS.Helpers.Exceptions.ArgumentNullException else require("./custom_exception").ArgumentNullException
-  proxyValidationContainer = {}
-  # Get the domains definition.
-  class MetadataBuilder
+# Exceptions
+ArgumentNullException = require("./custom_exception").ArgumentNullException
+proxyValidationContainer = {}
+# Get the domains definition.
+class MetadataBuilder
+
+  #Initialize the dependencies from the project: the domains and the metadatas.
+  initialize: (options, cb) ->
+    throw new ArgumentNullException('The metadata builder needs options with domains and metadatas.') if not options?
+    throw new ArgumentNullException('The metadata builder needs domains in options.') if not options.domains?
+    throw new ArgumentNullException('The metadata builder needs metadatas in options.') if not options.metadatas?
+    @domains = options.domains
+    @metadatas = options.metadatas
+    @isLog = options.isLog #Define if there is warn or not for metadata properties.
+    cb(@domains, @metadatas) if cb?

-    #Initialize the dependencies from the project: the domains and the metadatas.
-    initialize: (options, cb) ->
-      throw new ArgumentNullException('The metadata builder needs options with domains and metadatas.') if not options?
-      throw new ArgumentNullException('The metadata builder needs domains in options.') if not options.domains?
-      throw new ArgumentNullException('The metadata builder needs metadatas in options.') if not options.metadatas?
-      @domains = options.domains
-      @metadatas = options.metadatas
-      @isLog = options.isLog #Define if there is warn or not for metadata properties.
-      cb(@domains, @metadatas) if cb?
-
-    # Get the domains as they were initialized in the builder (if initialized).
-    getDomains: ->
-      return _.clone(@domains)
-
-    # Get the validation attributes from the domain.
-    getDomainsValidationAttrs: (model) ->
-      #console.log('called')
-      return new ArgumentNullException('The model should exists and have a metadatas property.') if not model?
-      #Get the metadatas from the model.
-      metadatas = @getMetadatas(model)
-      #Container for the validation rules of the domain of each property.
-      valDomAttrs = {}
-      # Lopping through all attributes of th model in order to build their validators.
-      for attr of metadatas
-        # Construct the metadata object
-        md = metadatas[attr] or {}
-        if (md.isValidationOff? and md.isValidationOff is false) or (not md.isValidationOff?)
-          ###_.extend(metadata, md.metadata) if md.metadata?
-          (metadata.domain = md.domain) if md.domain?
-          (metadata.required = md.required) if md.required?
-          ###
-          #Container for the validators.
-          validators = []
-          #If the required filed is set, add a validator
-          validators.push({"type": "required","value": true}) if md.required is true
-          # Extend the validators
-          (validators = _.union(validators, @domains[md.domain].validation)) if md.domain? and @domains[md.domain]?
-          # Set the validators inide the container associated with the field.
-          valDomAttrs[attr] = validators
-      return valDomAttrs
-    # Get all the metadatas from the model.
-    getMetadatas: (model) ->
-      throw new ArgumentNullException("In order to get metadatas , you must provide a model.") if not model?
-      # Construct the metadatas obtained with the model name.
-      entityMetadatas = @constructEntityMetaDatas(model)
-      metadatas = _.clone(entityMetadatas)
-      # If the models has metadatas the metadatas given by its definitions will extend them.
-      metadatasAttrs = _.keys(metadatas)
-      if model.metadatas?
-        metadatasAttrs = _.union(metadatasAttrs, _.keys(model.metadatas))
-      for mdlMetadataAttr in metadatasAttrs
-        entityAttrMetadata = entityMetadatas[mdlMetadataAttr] # Get the introspected metadatas.
-        mdlMetadata = if model.metadatas? and model.metadatas[mdlMetadataAttr]? then model.metadatas[mdlMetadataAttr] else undefined
-        metadata = {} # Create a container for the metadatas.
-        _.extend(metadata,  entityAttrMetadata) # Inject validationinto metadata the entitydefinitions metadata attributes.
-        #console.log("metadata",metadata,  metadata.domain, "domains", _.omit(@domains[metadata.domain]))
-        _.extend(metadata, _.omit(@domains[metadata.domain], 'validation')) #override the metadata with the metadata inside the domain style, validators,....
-        if mdlMetadata?
-          _.extend(metadata, mdlMetadata.metadata)  if mdlMetadata.metadata?
-          _.extend(metadata, _.omit(@domains[metadata.domain], 'validation')) #override the metadata with the metadata inside the domain style, validators,....
-          # Extend the "overriden" metadatas
-          # Property that can be overriden: required, label, domain, isValidationOff
-          overridenProperties = {}
-          #console.log 'mdlMetadatamdlMetadata', mdlMetadata
-          if mdlMetadata.domain?
-            _.extend(overridenProperties, {domain: mdlMetadata.domain}) # Change the domain.
-            _.extend(overridenProperties, _.omit(@domains[mdlMetadata.domain], 'validation')) # Change the metadatas of the domain.
-          _.extend(overridenProperties, {required: mdlMetadata.required}) if mdlMetadata.required?
-          _.extend(overridenProperties, {label: mdlMetadata.label}) if mdlMetadata.label?
-          _.extend(overridenProperties, {isValidationOff: mdlMetadata.isValidationOff}) if mdlMetadata.isValidationOff? #Turn off the model validations.
-          _.extend(overridenProperties, {style: mdlMetadata.style}) if mdlMetadata.style?
-          _.extend(overridenProperties, {decorator: mdlMetadata.decorator}) if mdlMetadata.decorator?
-          _.extend(overridenProperties, {symbol: mdlMetadata.symbol}) if mdlMetadata.symbol?
-          _.extend(overridenProperties, {decoratorOptions: mdlMetadata.decoratorOptions}) if mdlMetadata.decoratorOptions?
-          _.extend(overridenProperties, {idAttribute: mdlMetadata.idAttribute}) if mdlMetadata.idAttribute?
-          # If at least one property has been defined.
-          _.extend(metadata, overridenProperties) if not _.isEmpty(overridenProperties)
-        #Update the global metadatas<
-        #console.log "metadata", metadata
-        metadatas[mdlMetadataAttr] = metadata
-      return metadatas
+  # Get the domains as they were initialized in the builder (if initialized).
+  getDomains: ->
+    return _.clone(@domains)

-    #Get the attributes for one property of a metadata.
-    getMetadataForAttribute: (model, attribute) ->
-      throw new ArgumentNullException("In order to get metadatas for an attribute of a model , you must provide a model.") if not model?
-      throw new ArgumentNullException("In order to get metadatas for an attribute of a model , you must provide an attribute.") if not attribute?
-      entityAttrMetadata = @constructEntityMetaDatas(model)[attribute]
-      mdlMetadata = if model.metadatas? and model.metadatas[attribute]? then model.metadatas[attribute] else undefined
+  # Get the validation attributes from the domain.
+  getDomainsValidationAttrs: (model) ->
+    #console.log('called')
+    return new ArgumentNullException('The model should exists and have a metadatas property.') if not model?
+    #Get the metadatas from the model.
+    metadatas = @getMetadatas(model)
+    #Container for the validation rules of the domain of each property.
+    valDomAttrs = {}
+    # Lopping through all attributes of th model in order to build their validators.
+    for attr of metadatas
+      # Construct the metadata object
+      md = metadatas[attr] or {}
+      if (md.isValidationOff? and md.isValidationOff is false) or (not md.isValidationOff?)
+        ###_.extend(metadata, md.metadata) if md.metadata?
+        (metadata.domain = md.domain) if md.domain?
+        (metadata.required = md.required) if md.required?
+        ###
+        #Container for the validators.
+        validators = []
+        #If the required filed is set, add a validator
+        validators.push({"type": "required","value": true}) if md.required is true
+        # Extend the validators
+        (validators = _.union(validators, @domains[md.domain].validation)) if md.domain? and @domains[md.domain]?
+        # Set the validators inide the container associated with the field.
+        valDomAttrs[attr] = validators
+    return valDomAttrs
+  # Get all the metadatas from the model.
+  getMetadatas: (model) ->
+    throw new ArgumentNullException("In order to get metadatas , you must provide a model.") if not model?
+    # Construct the metadatas obtained with the model name.
+    entityMetadatas = @constructEntityMetaDatas(model)
+    metadatas = _.clone(entityMetadatas)
+    # If the models has metadatas the metadatas given by its definitions will extend them.
+    metadatasAttrs = _.keys(metadatas)
+    if model.metadatas?
+      metadatasAttrs = _.union(metadatasAttrs, _.keys(model.metadatas))
+    for mdlMetadataAttr in metadatasAttrs
+      entityAttrMetadata = entityMetadatas[mdlMetadataAttr] # Get the introspected metadatas.
+      mdlMetadata = if model.metadatas? and model.metadatas[mdlMetadataAttr]? then model.metadatas[mdlMetadataAttr] else undefined
       metadata = {} # Create a container for the metadatas.
       _.extend(metadata,  entityAttrMetadata) # Inject validationinto metadata the entitydefinitions metadata attributes.
       #console.log("metadata",metadata,  metadata.domain, "domains", _.omit(@domains[metadata.domain]))
@@ -118,42 +79,71 @@
         _.extend(overridenProperties, {decorator: mdlMetadata.decorator}) if mdlMetadata.decorator?
         _.extend(overridenProperties, {symbol: mdlMetadata.symbol}) if mdlMetadata.symbol?
         _.extend(overridenProperties, {decoratorOptions: mdlMetadata.decoratorOptions}) if mdlMetadata.decoratorOptions?
+        _.extend(overridenProperties, {idAttribute: mdlMetadata.idAttribute}) if mdlMetadata.idAttribute?
         # If at least one property has been defined.
         _.extend(metadata, overridenProperties) if not _.isEmpty(overridenProperties)
-      return metadata
-    constructEntityMetaDatas: (model) ->
-      @metadatas = @metadatas || {} # If no metadatas have been defined.
-      # TODO: pbn => Use a flatten function in order to flatten @metadatas and be ablt to access it without any problem.
-      if model.modelName?
-        mdName = model.modelName.split('.')
-        if mdName.length is 1
-          if @metadatas[model.modelName]?
-            return @metadatas[model.modelName]
-          else
-            if @isLog
-              console.warn("The metadatas does not have properties for model '#{model.modelName}'.")
-            return {}
+      #Update the global metadatas<
+      #console.log "metadata", metadata
+      metadatas[mdlMetadataAttr] = metadata
+    return metadatas
+
+  #Get the attributes for one property of a metadata.
+  getMetadataForAttribute: (model, attribute) ->
+    throw new ArgumentNullException("In order to get metadatas for an attribute of a model , you must provide a model.") if not model?
+    throw new ArgumentNullException("In order to get metadatas for an attribute of a model , you must provide an attribute.") if not attribute?
+    entityAttrMetadata = @constructEntityMetaDatas(model)[attribute]
+    mdlMetadata = if model.metadatas? and model.metadatas[attribute]? then model.metadatas[attribute] else undefined
+    metadata = {} # Create a container for the metadatas.
+    _.extend(metadata,  entityAttrMetadata) # Inject validationinto metadata the entitydefinitions metadata attributes.
+    #console.log("metadata",metadata,  metadata.domain, "domains", _.omit(@domains[metadata.domain]))
+    _.extend(metadata, _.omit(@domains[metadata.domain], 'validation')) #override the metadata with the metadata inside the domain style, validators,....
+    if mdlMetadata?
+      _.extend(metadata, mdlMetadata.metadata)  if mdlMetadata.metadata?
+      _.extend(metadata, _.omit(@domains[metadata.domain], 'validation')) #override the metadata with the metadata inside the domain style, validators,....
+      # Extend the "overriden" metadatas
+      # Property that can be overriden: required, label, domain, isValidationOff
+      overridenProperties = {}
+      #console.log 'mdlMetadatamdlMetadata', mdlMetadata
+      if mdlMetadata.domain?
+        _.extend(overridenProperties, {domain: mdlMetadata.domain}) # Change the domain.
+        _.extend(overridenProperties, _.omit(@domains[mdlMetadata.domain], 'validation')) # Change the metadatas of the domain.
+      _.extend(overridenProperties, {required: mdlMetadata.required}) if mdlMetadata.required?
+      _.extend(overridenProperties, {label: mdlMetadata.label}) if mdlMetadata.label?
+      _.extend(overridenProperties, {isValidationOff: mdlMetadata.isValidationOff}) if mdlMetadata.isValidationOff? #Turn off the model validations.
+      _.extend(overridenProperties, {style: mdlMetadata.style}) if mdlMetadata.style?
+      _.extend(overridenProperties, {decorator: mdlMetadata.decorator}) if mdlMetadata.decorator?
+      _.extend(overridenProperties, {symbol: mdlMetadata.symbol}) if mdlMetadata.symbol?
+      _.extend(overridenProperties, {decoratorOptions: mdlMetadata.decoratorOptions}) if mdlMetadata.decoratorOptions?
+      # If at least one property has been defined.
+      _.extend(metadata, overridenProperties) if not _.isEmpty(overridenProperties)
+    return metadata
+  constructEntityMetaDatas: (model) ->
+    @metadatas = @metadatas || {} # If no metadatas have been defined.
+    # TODO: pbn => Use a flatten function in order to flatten @metadatas and be ablt to access it without any problem.
+    if model.modelName?
+      mdName = model.modelName.split('.')
+      if mdName.length is 1
+        if @metadatas[model.modelName]?
+          return @metadatas[model.modelName]
         else
-          if @metadatas[mdName[0]]? and @metadatas[mdName[0]][mdName[1]]?
-            return @metadatas[mdName[0]][mdName[1]]
-          else
-            if @isLog
-              console.warn("The metadatas does not have properties for model '#{model.modelName}'.")
-            return {}
+          if @isLog
+            console.warn("The metadatas does not have properties for model '#{model.modelName}'.")
+          return {}
       else
-        throw new ArgumentNullException('The model should have a model name in order to build its metadatas')
+        if @metadatas[mdName[0]]? and @metadatas[mdName[0]][mdName[1]]?
+          return @metadatas[mdName[0]][mdName[1]]
+        else
+          if @isLog
+            console.warn("The metadatas does not have properties for model '#{model.modelName}'.")
+          return {}
+    else
+      throw new ArgumentNullException('The model should have a model name in order to build its metadatas')

-    proxyDomainValidationAttrs: (model) ->
+  proxyDomainValidationAttrs: (model) ->
+    return getDomainsValidationAttrs(model)
+    return proxyValidationContainer[model.modelName] if(model.modelName? and proxyValidationContainer[model.modelName]?)
+    if model.modelName?
+      return proxyValidationContainer[model.modelName] = getDomainsValidationAttrs(model)
+    else
       return getDomainsValidationAttrs(model)
-      return proxyValidationContainer[model.modelName] if(model.modelName? and proxyValidationContainer[model.modelName]?)
-      if model.modelName?
-        return proxyValidationContainer[model.modelName] = getDomainsValidationAttrs(model)
-      else
-        return getDomainsValidationAttrs(model)
-  if isInBrowser
-    NS.Helpers = NS.Helpers or {}
-    NS.Helpers.MetadataBuilder = MetadataBuilder
-    NS.Helpers.metadataBuilder = new MetadataBuilder()
-  else
-    module.exports = {MetadataBuilder: MetadataBuilder, metadataBuilder: new MetadataBuilder()}
-)(if typeof module is 'undefined' and typeof window isnt 'undefined' then window.Fmk else module.exports)+module.exports = {MetadataBuilder: MetadataBuilder, metadataBuilder: new MetadataBuilder()}


Element.Tag.query.getSelectorPriority() added

--- Element/element/tag/query.coffee
+++ Element/element/tag/query.coffee
@@ -6,6 +6,10 @@

 {emitSignal} = signal.Emitter
 Tag = Text = null
+
+DEFAULT_PRIORITY = 0
+ELEMENTS_PRIORITY = 1
+ATTRS_PRIORITY = 100

 test = (node, funcs, index, targetFunc, targetCtx, single) ->
 	while index < funcs.length
@@ -37,6 +41,7 @@
 					return true
 	false
 anyDescendant.isIterator = true
+anyDescendant.priority = DEFAULT_PRIORITY
 anyDescendant.toString = -> 'anyDescendant'

 directParent = (node, funcs, index, targetFunc, targetCtx, single) ->
@@ -47,6 +52,7 @@
 			return directParent parent, funcs, index, targetFunc, targetCtx, single
 	false
 directParent.isIterator = true
+directParent.priority = DEFAULT_PRIORITY
 directParent.toString = -> 'directParent'

 anyChild = (node, funcs, index, targetFunc, targetCtx, single) ->
@@ -61,6 +67,7 @@
 					return true
 	false
 anyChild.isIterator = true
+anyChild.priority = DEFAULT_PRIORITY
 anyChild.toString = -> 'anyChild'

 anyParent = (node, funcs, index, targetFunc, targetCtx, single) ->
@@ -71,6 +78,7 @@
 			return anyParent(parent, funcs, index, targetFunc, targetCtx, single)
 	false
 anyParent.isIterator = true
+anyParent.priority = DEFAULT_PRIORITY
 anyParent.toString = -> 'anyParent'

 byName = (node, data1) ->
@@ -79,16 +87,19 @@
 	else if data1 is '#text' and node instanceof Text
 		true
 byName.isIterator = false
+byName.priority = ELEMENTS_PRIORITY
 byName.toString = -> 'byName'

 byInstance = (node, data1) ->
 	node instanceof data1
 byInstance.isIterator = false
+byInstance.priority = DEFAULT_PRIORITY
 byInstance.toString = -> 'byInstance'

 byTag = (node, data1) ->
 	node is data1
 byTag.isIterator = false
+byTag.priority = DEFAULT_PRIORITY
 byTag.toString = -> 'byTag'

 byAttr = (node, data1) ->
@@ -97,6 +108,7 @@
 	else
 		false
 byAttr.isIterator = false
+byAttr.priority = ATTRS_PRIORITY
 byAttr.toString = -> 'byAttr'

 byAttrValue = (node, data1, data2) ->
@@ -105,6 +117,7 @@
 	else
 		false
 byAttrValue.isIterator = false
+byAttrValue.priority = ATTRS_PRIORITY
 byAttrValue.toString = -> 'byAttrValue'

 byAttrStartsWithValue = (node, data1, data2) ->
@@ -114,6 +127,7 @@
 			return attr.indexOf(data2) is 0
 	false
 byAttrStartsWithValue.isIterator = false
+byAttrStartsWithValue.priority = ATTRS_PRIORITY
 byAttrStartsWithValue.toString = -> 'byAttrStartsWithValue'

 byAttrEndsWithValue = (node, data1, data2) ->
@@ -123,6 +137,7 @@
 			return attr.indexOf(data2, attr.length - data2.length) > -1
 	false
 byAttrEndsWithValue.isIterator = false
+byAttrEndsWithValue.priority = ATTRS_PRIORITY
 byAttrEndsWithValue.toString = -> 'byAttrEndsWithValue'

 byAttrContainsValue = (node, data1, data2) ->
@@ -132,6 +147,7 @@
 			return attr.indexOf(data2) > -1
 	false
 byAttrContainsValue.isIterator = false
+byAttrContainsValue.priority = ATTRS_PRIORITY
 byAttrContainsValue.toString = -> 'byAttrContainsValue'

 TYPE = /^#?[a-zA-Z0-9|\-:_]+/
@@ -471,3 +487,15 @@
 			break
 		sum += query.length
 	sum
+
+module.exports.getSelectorPriority = (selector, beginQuery=0, endQuery=Infinity) ->
+	sum = 0
+	queries = getQueries selector, 0
+	for query, i in queries
+		if i < beginQuery
+			continue
+		if i >= endQuery
+			break
+		for func in query by 3
+			sum += func.priority
+	sum


Update matched event

Update matched event

--- lib/keybinding-resolver-view.coffee
+++ lib/keybinding-resolver-view.coffee
@@ -33,46 +33,47 @@

   attach: ->
     atom.workspaceView.prependToBottom(this)
-    @subscribe atom.keymap, "key-binding-matched", (usedKeyBinding, unusedKeyBindings) =>
-      @update(usedKeyBinding, unusedKeyBindings)
+    @subscribe atom.keymap, "matched", ({keystrokes, binding, keyboardEventTarget}) =>
+      @update(keystrokes, binding, keyboardEventTarget)

-    @subscribe atom.keymap, "parital-key-bindings-matched", (keystrokes, keyBindings) =>
+    @subscribe atom.keymap, "matched-partially", ({keystrokes, partiallyMatchedBindings, keyboardEventTarget}) =>
       @updatePartial(keystrokes, keyBindings)

-    @subscribe atom.keymap, "no-key-binding-matched", (keystrokes) =>
-      @keystroke.html $$ -> @span class: 'keystroke', "#{keystrokes}"
-      @commands.empty()
+    @subscribe atom.keymap, "match-failed", ({keystrokes, keyboardEventTarget}) =>
+      @update(keystrokes, null, keyboardEventTarget)

   detach: ->
     super
     @unsubscribe()

-  update: (usedKeyBinding, unusedKeyBindings) ->
+  update: (keystrokes, keyBinding, keyboardEventTarget) ->
     @keystroke.html $$ ->
-      @span class: 'keystroke', usedKeyBinding.keystrokes
+      @span class: 'keystroke', keystrokes
+
+    unusedKeyBindings = atom.keymap.findKeyBindings({keystrokes, target: keyboardEventTarget}).filter (binding) ->
+      binding != keyBinding
+
+    unmatchedKeyBindings = atom.keymap.findKeyBindings({keystrokes}).filter (binding) ->
+      binding != keyBinding and keyBinding not in unusedKeyBindings

     @commands.html $$ ->
       @table class: 'table-condensed', =>
         @tr class: 'used', =>
-          @td class: 'command', usedKeyBinding.command
-          @td class: 'selector', usedKeyBinding.selector
-          @td class: 'source', usedKeyBinding.source
+          @td class: 'command', keyBinding.command
+          @td class: 'selector', keyBinding.selector
+          @td class: 'source', keyBinding.source

-        for keyBinding in unusedKeyBindings
+        for binding in unusedKeyBindings
           @tr class: 'unused', =>
-            @td class: 'command', keyBinding.command
-            @td class: 'selector', keyBinding.selector
-            @td class: 'source', keyBinding.source
+            @td class: 'command', binding.command
+            @td class: 'selector', binding.selector
+            @td class: 'source', binding.source

-        matchedKeyBindings = atom.keymap.findKeyBindings(keystrokes: usedKeyBinding.keystrokes)
-        matchedKeyBindings = matchedKeyBindings.filter (keyBinding) ->
-          keyBinding != usedKeyBinding and keyBinding not in unusedKeyBindings
-
-        for keyBinding in matchedKeyBindings
+        for binding in unmatchedKeyBindings
           @tr class: 'unmatched', =>
-            @td class: 'command', keyBinding.command
-            @td class: 'selector', keyBinding.selector
-            @td class: 'source', keyBinding.source
+            @td class: 'command', binding.command
+            @td class: 'selector', binding.selector
+            @td class: 'source', binding.source

   updatePartial: (keystrokes, keyBindings) ->
     @keystroke.html $$ ->


Remove Analytics tracking code to resolve a deploy error

--- source/assets/javascripts/footer.js.coffee
+++ source/assets/javascripts/footer.js.coffee
@@ -1,9 +0,0 @@
-# GOOGLE ANALYTICS TRACKING CODE
-<script>
-  (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
-  (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
-  m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
-  })(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
-  ga('create', 'UA-69304119-3', 'auto');
-  ga('send', 'pageview');
-</script>


yay. some tests are passing!

yay. some tests are passing!

--- source/javascripts/spec/spec_xlform.coffee
+++ source/javascripts/spec/spec_xlform.coffee
@@ -262,14 +262,14 @@
     $el.appendTo(div)

     expect(div.html()).not.toContain("empty")
-    expect(div.find("li.xlf-row-view").length).toBe(2)
+    expect(div.find("li.xlf-row-view").length).toBe(1)

     clickNewRow = ()->
       div.find("button#add-question").click()
     expect(clickNewRow).not.toThrow()

-    # expect(div.find("li.xlf-row-view").length).toBe(2)
-
-    # finalRow = div.find("li.xlf-row-view").eq(-1)
-    # expect(finalRow.find(".xlf-dv-label").length).toBe(1)
-    # expect(finalRow.find(".xlf-dv-label blockquote").length).toBe(1)+    expect(div.find("li.xlf-row-view").length).toBe(2)
+
+    finalRow = div.find("li.xlf-row-view").eq(0)
+    expect(finalRow.find(".xlf-dv-label").length).toBe(1)
+    expect(finalRow.find(".xlf-dv-label blockquote").length).toBe(1)


Updated gruntfile to copy context first.

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -50,8 +50,8 @@

 	grunt.registerTask 'base', [
 		'jsonlint:context'
+		'copy:context'
 		'build'
-		'copy:context'
 		'mochaTest:server'
 		'karma:unit'
 	]


Fix tasks

Fix tasks

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -3,6 +3,8 @@
 module.exports = ( grunt ) ->

     grunt.initConfig
+        clean:
+            test: [ "test/fixtures" ]
         coffeelint:
             options:
                 arrow_spacing:
@@ -50,9 +52,10 @@
                 tasks: [
                     "coffeelint"
                     "coffee"
-                    "nodeunit"
+                    "test"
                 ]

+    grunt.loadNpmTasks "grunt-contrib-clean"
     grunt.loadNpmTasks "grunt-contrib-nodeunit"
     grunt.loadNpmTasks "grunt-contrib-watch"
     grunt.loadNpmTasks "grunt-contrib-coffee"
@@ -61,6 +64,12 @@
     grunt.registerTask "default", [
         "coffeelint"
         "coffee"
+        "clean"
+        "test"
+    ]
+
+    grunt.registerTask "test", [
+        "clean"
         "nodeunit"
     ]


Use PhantomJS.

Use PhantomJS.

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -59,22 +59,22 @@
         karma:
             unit:
                 configFile: 'test/configs/unit.conf.coffee'
-                browsers: ['Chrome']
+                browsers: ['PhantomJS']
                 background: true
             unitci_firefox:
                 configFile: 'test/configs/unit.conf.coffee'
-                browsers: ['Firefox']
+                browsers: ['Firefox', 'PhantomJS']
                 singleRun: true
                 reporters: ['dots', 'junit']
                 junitReporter:
                     outputFile: 'unit-results.xml'
             e2e:
                 configFile: 'test/configs/e2e.conf.coffee'
-                browsers: ['Chrome']
+                browsers: ['PhantomJS']
                 background: true
             e2eci_firefox:
                 configFile: 'test/configs/e2e.conf.coffee'
-                browsers: ['Firefox']
+                browsers: ['Firefox', 'PhantomJS']
                 singleRun: true
                 reporters: ['dots', 'junit']
                 junitReporter:


Missed this one

Missed this one

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -25,14 +25,27 @@
     return

   # Load configuration.
+  fs = require 'fs'
+
+  yaml = require 'js-yaml'
+
   config = require 'config'

-  Platform = require 'platform'
-  Platform.set 'native'
-  platform = Platform.get()
+  # Set environment variables into config.
+  config.set 'path', __dirname
+  config.set k, v for k, v of process.env

-  # Load configuration.
-  platform.loadConfig config
+  # Read configuration file.
+  settingsFilename = config.get 'path'
+  settingsFilename += '/config/settings.yml'
+
+  throw new Error '
+    Settings file not found!
+    You should copy config/default.settings.yml to config/settings.yml
+  ' unless fs.existsSync settingsFilename
+
+  settings = yaml.safeLoad fs.readFileSync settingsFilename, 'utf8'
+  config.set k, v for k, v of settings

   # Register the configured packages.
   pkgman = require 'pkgman'