Update history when active screen changes

--- client/lanes/react/Viewport.coffee
+++ client/lanes/react/Viewport.coffee
@@ -22,7 +22,7 @@
         rootProps:      'object'
         rootElement:    'object'
         history:        'object'
-        useHistory:     'boolean'
+        useHistory:     {type: 'boolean', default: true}

     constructor: ->
         super
@@ -52,7 +52,6 @@
             @initializeHistory()
         else
             @history = new FakeHistory
-
         if prev.hasClass('loading')
             prev.addClass('complete')
             _.delay(->
@@ -70,6 +69,13 @@

         Lanes.Screens.Definitions.setBrowserLocation(
             @history.getCurrentLocation()
+        )
+
+        Lanes.Screens.Definitions.displaying.on('change:active', (screen) =>
+            if screen.active
+                @history.replace(screen.historyUrl())
+            if Lanes.Screens.Definitions.displaying.length is 0
+                @history.push('/')
         )

     close: ->


more gas

more gas

--- lib/token.coffee
+++ lib/token.coffee
@@ -100,7 +100,7 @@
     .then (@recipientBalance) =>
       d {@recipientBalance}
       d { sender, recipient, amount }
-      tokenContract.issue recipient, amount, proofId, from: sender, gas: 2e5
+      tokenContract.issue recipient, amount, proofId, from: sender, gas: 5e5
     .then (@transactionId) =>
       d {@transactionId}
       tokenContract.balanceOf.call recipient


Remove log

Remove log

--- client/startup/userTimezone.coffee
+++ client/startup/userTimezone.coffee
@@ -3,5 +3,4 @@
 	if user?.status is 'online'
 		utcOffset = moment().utcOffset() / 60
 		if user.utcOffset isnt utcOffset
-			console.log 'updateUserUtcOffset', utcOffset
 			Meteor.call 'updateUserUtcOffset', utcOffset


code cleaning: sub-functions for init

code cleaning: sub-functions for init

--- lib/token.coffee
+++ lib/token.coffee
@@ -106,39 +106,55 @@
             permissions[body.name][docType.toLowerCase()] = description

+## function initHomeProxy (callback)
+## @callback {function} Continuation to pass control back to when complete
+## Initialize tokens and permissions for Home and Proxy
+initHomeProxy = (callback) ->
+    token = process.env.token
+    token = token.split('\n')[0]
+    # Add home token and permissions
+    tokens['home'] = token
+    permissions.home =
+        "application": "authorized"
+        "notification": "authorized"
+        "user": "authorized"
+        "alarm": "authorized"
+        "cozyinstance": "authorized"
+        "encryptedkeys": "authorized"
+    # Add proxy token and permissions
+    tokens['proxy'] = token
+    permissions.proxy =
+        "user": "authorized"
+    callback null
+
+
+## function initApplication (callback)
+## @appli {Object} Application
+## @callback {function} Continuation to pass control back to when complete
+## Initialize tokens and permissions for application
+initApplication = (appli, callback) ->
+    if appli.state is "installed"
+        tokens[appli.name] = appli.password
+        if appli.permissions? and appli.permissions isnt null
+            permissions[appli.name] = {}
+            for docType, description of appli.permissions
+                docType = docType.toLowerCase()
+                permissions[appli.name][docType] = description
+            callback null
+
+
 ## function init (callback)
 ## @callback {function} Continuation to pass control back to when complete.
 ## Initialize tokens which contains applications and their tokens
 module.exports.init = (callback) ->
-    tokens = []
     # Read shared token
     if process.env.NODE_ENV is "production"
-        token = process.env.token
-        token = token.split('\n')[0]
-        # Add home token and permissions
-        tokens['home'] = token
-        permissions.home =
-            "application": "authorized"
-            "notification": "authorized"
-            "user": "authorized"
-            "alarm": "authorized"
-            "cozyinstance": "authorized"
-            "encryptedkeys": "authorized"
-        # Add proxy token and permissions
-        tokens['proxy'] = token
-        permissions.proxy =
-            "user": "authorized"
-        # Add token and permissions for other started applications
-        db.view 'application/all', (err, res) ->
-            if err then callback new Error("Error in view")
-            else
-                # Search application
-                res.forEach (appli) ->
-                    if appli.state is "installed"
-                        tokens[appli.name] = appli.password
-                        if appli.permissions? and appli.permissions isnt null
-                            permissions[appli.name] = {}
-                            for docType, description of appli.permissions
-                                docType = docType.toLowerCase()
-                                permissions[appli.name][docType] = description
-                callback tokens, permissions
+        initHomeProxy () ->
+            # Add token and permissions for other started applications
+            db.view 'application/all', (err, res) ->
+                if err then callback new Error("Error in view")
+                else
+                    # Search application
+                    res.forEach (appli) ->
+                        initApplication appli, () ->
+                    callback tokens, permissions


Removed temporary testing code

Removed temporary testing code

--- lib/utils.coffee
+++ lib/utils.coffee
@@ -21,7 +21,7 @@
     return _.defaults(Utils.bbCallback((err, model, resp, modified_options) -> callback(err, model, resp, options)), options)

   # @private
-  @guid = -> _.uniqueId() # return (S4()+S4()+"-"+S4()+"-"+S4()+"-"+S4()+"-"+S4()+S4()+S4())
+  @guid = -> return (S4()+S4()+"-"+S4()+"-"+S4()+"-"+S4()+"-"+S4()+S4()+S4())

   # @private
   @parseUrl: (url) ->


Crop only horizontal or vertical borders

This heuristics tries to avoid cropping background, only
cropping common video covers cases. Also, changed to crop
a max of 25% of total original image.

--- components/GetBorderlessBox.coffee
+++ components/GetBorderlessBox.coffee
@@ -139,17 +139,34 @@
       else
         break

-    bbox.height -= bbox.y
-    bbox.width -= bbox.x
+    croppedBbox =
+      x: 0
+      y: 0
+      width: canvas.width
+      height: canvas.height
+
+    verticalVariation = Math.min bbox.y, croppedBbox.height - bbox.height
+    horizontalVariation = Math.min bbox.x, croppedBbox.width - bbox.width
+
+    if verticalVariation > horizontalVariation * 0.5
+      croppedBbox.y = verticalVariation
+      croppedBbox.height = croppedBbox.height - verticalVariation
+      croppedBbox.height -= croppedBbox.y
+    if horizontalVariation > verticalVariation * 0.5
+      croppedBbox.x = horizontalVariation
+      croppedBbox.width = croppedBbox.width - horizontalVariation
+      croppedBbox.width -= croppedBbox.x
+
     # Check for invalid bboxes (e.g. images with one color, small bboxes)
-    if bbox.height * bbox.width <= 0.1 * gray.length or
-    bbox.width < 0 or
-    bbox.height < 0
-      bbox =
+    if (croppedBbox.height * croppedBbox.width) <= (0.25 * gray.length) or
+        croppedBbox.width < 0 or
+        croppedBbox.height < 0
+      croppedBbox =
         x: 0
         y: 0
         width: canvas.width
         height: canvas.height
-    out.send bbox
+
+    out.send croppedBbox
     do callback
   c


Never delete the reference to the nested view, only unbind and rebind. [#344]

--- src/binders.coffee
+++ src/binders.coffee
@@ -86,6 +86,7 @@
       declaration = el.getAttribute attr

       @marker = document.createComment " rivets: #{@type} #{declaration} "
+      @bound = false

       el.removeAttribute attr
       el.parentNode.insertBefore @marker, el
@@ -95,7 +96,7 @@
     @nested?.unbind()

   routine: (el, value) ->
-    if !!value is not @nested?
+    if !!value is not @bound
       if value
         models = {}
         models[key] = model for key, model of @view.models
@@ -106,11 +107,13 @@
           adapters: @view.options.adapters
           config: @view.options.config

-        (@nested = new Rivets.View(el, models, options)).bind()
+        (@nested or= new Rivets.View(el, models, options)).bind()
         @marker.parentNode.insertBefore el, @marker.nextSibling
+        @bound = true
       else
         el.parentNode.removeChild el
-        delete @nested
+        @nested.unbind()
+        @bound = false

   update: (models) ->
     @nested?.update models


Don't barf on unknown includes

Don't barf on unknown includes

--- src/enfield.coffee
+++ src/enfield.coffee
@@ -416,7 +416,7 @@

   includes = {}
   for filepath in sorted
-    continue if filepath of includes
+    continue if (filepath of includes) or not (filepath of contents)
     includes[filepath] = tinyliquid.parse(contents[filepath], files: includes).code

   includes


Optimize main loop for Node.

Optimize main loop for Node.

Not as good as pre-compilation, but a definite boost.

--- src/methods.coffee
+++ src/methods.coffee
@@ -695,7 +695,7 @@
         cf = rs.curr_frame()
         pc = rs.curr_pc()
         op = code[pc]
-        unless RELEASE?
+        unless RELEASE? or util.log_level <= util.ERROR
           throw "#{@name}:#{pc} => (null)" unless op
           debug "#{padding}stack: [#{pa cf.stack}], local: [#{pa cf.locals}]"
           annotation =


Activity: socialapicontroller failsafe start for prefetcheddata

--- client/Main/socialapicontroller.coffee
+++ client/Main/socialapicontroller.coffee
@@ -12,15 +12,15 @@

   getPrefetchedData: (dataPath) ->

-    return  unless KD.socialApiData
-    return  unless data = KD.socialApiData[dataPath]
+    return [] unless KD.socialApiData
+    return [] unless data = KD.socialApiData[dataPath]

     fn = switch dataPath
       when 'popularTopics', 'followedChannels' then mapChannels
       when 'publicFeed', 'pinnedMessages'      then mapActivities
       when 'privateMessages'                   then mapPrivateMessages

-    return fn data
+    return fn(data) or []

   openGroupChannel: ->


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com


fix scroll

fix scroll

--- client/app/stores/message_store.coffee
+++ client/app/stores/message_store.coffee
@@ -251,7 +251,6 @@

     handleFetchResult = (result) ->

-        _messages  = _messages.clear()
         if result.links? and result.links.next?
             # reinit params here for pagination on filtered lists
             _params = {}


FIX: Revert to old status if modal was not presented

--- src/app/common/services/task-service.coffee
+++ src/app/common/services/task-service.coffee
@@ -322,7 +322,12 @@
     if requiresFileUpload
       oldStatus = task.status
       task.status = status
-      UploadSubmissionModal.show(task).result.then(
+      modal = UploadSubmissionModal.show(task)
+      # Modal failed to present
+      unless modal?
+        task.status = oldStatus
+        return
+      modal.result.then(
         # Grade was selected (modal closed with result)
         (response) ->
           # TODO: (@alexcu) Do something on success


FIX: remove space from end of comment

FIX: remove space from end of comment

--- src/app/common/services/unit-service.coffee
+++ src/app/common/services/unit-service.coffee
@@ -101,7 +101,7 @@
         # student doesn't exist - push it to the student list
         unit.students.push student
       else
-        # student exists - extend the student
+        # student exists - extend the student
         student = _.extend foundStudent, student
       unit.extendStudent student


Correctly process entries from retailer -> master

--- src/coffee/marketplace-stock-updater.coffee
+++ src/coffee/marketplace-stock-updater.coffee
@@ -75,22 +75,24 @@
             _.each mappedInventoryEntries, (entry) -> ieMaster.where("sku = \"#{entry.sku}\"")
             ieMaster.fetch()
             .then (result) =>
-              existingInventoryEntries = result.body.results
-              @logger?.debug existingInventoryEntries, "Found #{_.size existingInventoryEntries} matching inventory entries in master"
+              existingEntriesInMaster = result.body.results
+              @logger?.debug existingEntriesInMaster, "Found #{_.size existingEntriesInMaster} matching inventory entries in master"

-              Q.allSettled _.map existingInventoryEntries, (entry) =>
-                existingEntry = _.find mappedInventoryEntries, (e) -> e.sku is entry.sku
-                @logger?.debug existingEntry, "Found existing retailer entry for master sku #{entry.sku}"
-                if existingEntry?
-                  sync = @inventorySync.buildActions(existingEntry, entry)
+              Q.allSettled _.map mappedInventoryEntries, (retailerEntry) =>
+                masterEntry = _.find existingEntriesInMaster, (e) -> e.sku is retailerEntry.sku
+                if masterEntry?
+                  @logger?.debug masterEntry, "Found existing inventory entry in master for sku #{retailerEntry.sku}, about to build update actions"
+                  sync = @inventorySync.buildActions(retailerEntry, masterEntry)
                   if sync.get()
                     @summary.toUpdate++
                     sync.update()
                   else
+                    @logger?.debug masterEntry, "No update necessary for entry in master with sku #{retailerEntry.sku}"
                     Q()
                 else
+                  @logger?.debug "No inventory entry found in master for sku #{retailerEntry.sku}, about to create it"
                   @summary.toCreate++
-                  @masterClient.inventoryEntries.save(entry)
+                  @masterClient.inventoryEntries.save(retailerEntry)
               .then (results) =>
                 failures = []
                 _.each results, (result) =>


Activity: add to-do

Activity: add to-do

--- client/Social/Activity/AppView.coffee
+++ client/Social/Activity/AppView.coffee
@@ -167,6 +167,7 @@
     @warning   = new KDCustomHTMLView
     @goBack    = new KDButtonView
       cssClass : 'goback-button'
+      # todo - add group context here!
       callback : => KD.singletons.router.handleRoute '/Activity'

   pistachio:->


Add tool archetypes in reader; fails because it still creates duplicates.

--- client/code/view/dataset/tools.coffee
+++ client/code/view/dataset/tools.coffee
@@ -19,6 +19,8 @@
     views = @model.get('views').visible()
     views.each (view) =>
       @addToolInstance view
+    app.tools().each (archetype) =>
+      @addToolArchetype archetype
     @

   addToolArchetype: (toolModel) =>


Reset comment on new reviewResult

Reset comment on new reviewResult

--- client/components/ReviewResult.coffee
+++ client/components/ReviewResult.coffee
@@ -32,6 +32,15 @@
 import styles from './ReviewResult.css'

 TAIL_TEXTS = ["Решение проигнорировано", "Решение зачтено"]
+
+submitListEquals = (submits1, submits2) ->
+    if submits1.length != submits2.length
+        return false
+    for s, i in submits1
+        if s._id != submits2[i]._id
+            return false
+    return true
+

 class ProblemCommentsLists extends React.Component
     render: () ->
@@ -64,6 +73,12 @@
         @toggleBestSubmits = @toggleBestSubmits.bind this
         @downloadSubmits = @downloadSubmits.bind this
         @copyTest = @copyTest.bind this
+
+    componentDidUpdate: (prevProps, prevState) ->
+        if !submitListEquals(prevProps.submits, @props.submits)
+            @setState
+                commentText: ""
+                bestSubmits: false

     copyTest: (result) ->
         (e) =>
@@ -207,14 +222,7 @@
         @setCurrentDiff = @setCurrentDiff.bind this

     componentDidUpdate: (prevProps, prevState) ->
-        changed = false
-        if prevProps.submits.length != @props.submits.length
-            changed = true
-        else
-            for s, i in prevProps.submits
-                if s._id != @props.submits[i]._id
-                    changed = true
-        if changed
+        if !submitListEquals(prevProps.submits, @props.submits)
             @setState
                 currentSubmit: if @props.submits then @props.submits[@props.submits.length - 1] else null
                 currentDiff: [undefined, undefined]
@@ -246,6 +254,7 @@
             @setState
                 currentSubmit: undefined
                 currentDiff: newDiff
+
     render:  () ->
         SubmitComponent = @props.SubmitComponent
         allProps = {@props..., @state...}


Fixing indentation bug

Fixing indentation bug

--- src/components/multi_collection_view.coffee
+++ src/components/multi_collection_view.coffee
@@ -59,34 +59,34 @@
   getCollection: Luca.components.CollectionView::getCollection
 #### Private Helpers

-bubbleCollectionEvents = ()->
-  container = @
+  bubbleCollectionEvents = ()->
+    container = @
     container.eachComponent (component)->
       for eventId in ['refresh','before:refresh','after:refresh','empty:results']
         component.on eventId, ()->
           if component is container.activeComponent()
             container.trigger(eventId)

-propagateCollectionComponents = ()->
-  container = @
+  propagateCollectionComponents = ()->
+    container = @

-  # in the multi view will share the same
-  # collection, filter state, pagination options, etc
-  for component in @components
-    _.extend component,
-      collection: container.getCollection?() || @collection
-      getQuery: container.getQuery
-      getQueryOptions: container.getQueryOptions
+    # in the multi view will share the same
+    # collection, filter state, pagination options, etc
+    for component in @components
+      _.extend component,
+        collection: container.getCollection?() || @collection
+        getQuery: container.getQuery
+        getQueryOptions: container.getQueryOptions

-validateComponent = (component)->
-  type = (component.type || component.ctype)
+  validateComponent = (component)->
+    type = (component.type || component.ctype)

-  return if  type is "collection" or
-             type is "collection_view" or
-             type is "table" or
-             type is "table_view"
+    return if  type is "collection" or
+               type is "collection_view" or
+               type is "table" or
+               type is "table_view"

-  throw "The MultiCollectionView expects to contain multiple collection views"
+    throw "The MultiCollectionView expects to contain multiple collection views"

 Luca.components.MultiCollectionView.defaultTopToolbar =


Amend arrow spacing per coffeelint.

Amend arrow spacing per coffeelint.

--- src/Bacon.coffee
+++ src/Bacon.coffee
@@ -1268,7 +1268,7 @@
   i = patterns.length - 1
   while (i > 0)
     unless patterns[i] instanceof Function
-      patterns[i] = do(x=patterns[i])->(->x)
+      patterns[i] = do(x=patterns[i]) -> (-> x)
     patterns[i] = lateBindFirst patterns[i]
     i = i - 2
   withDescription(Bacon, "update", initial, patterns..., Bacon.when(patterns...).scan initial, ((x,f) -> f x))


simplify

simplify

Conflicts:
	src/Bacon.coffee

--- src/Bacon.coffee
+++ src/Bacon.coffee
@@ -919,10 +919,8 @@
 class Source
   constructor: (s) ->
     queue = []
-    isEnded = false
     @subscribe = s.subscribe
-    @markEnded = -> isEnded = true
-    @ended = -> isEnded
+    @markEnded = -> @ended = true
     if s instanceof Property
       @consume = () -> queue[0]
       @push  = (x) -> queue = [x]
@@ -932,7 +930,7 @@
     else
       @consume = () -> queue.shift()
       @push  = (x) -> queue.push(x)
-      @mayHave = (c) -> !isEnded || queue.length >= c
+      @mayHave = (c) -> !@ended || queue.length >= c
       @hasAtLeast = (c) -> queue.length >= c
       @sync = true

@@ -965,7 +963,7 @@
       match = (p) ->
         _.all(p.ixs, (i) -> sources[i.index].hasAtLeast(i.count))
       cannotSync = (source) ->
-        !source.sync or source.ended()
+        !source.sync or source.ended
       cannotMatch = (p) ->
         _.any(p.ixs, (i) -> !sources[i.index].mayHave(i.count))
       part = (source, sourceIndex) -> (unsubAll) ->


better error messaging

better error messaging

--- src/lib/controllers/image_controller.coffee
+++ src/lib/controllers/image_controller.coffee
@@ -40,9 +40,7 @@
     file_stream.on 'close', () ->
       # Now we need to convert the image to jpg and resize for thumbnails
       im.identify original_path, (err, features) ->
-        if err
-          return error_exit err
-        if features.format not of allowed_types
+        if err or features.format not of allowed_types
           return error_exit {msg: 'Only \'png\' or \'jpg\' photos may be uploaded at this time.'}
         im.convert [original_path, '-resize', '640', '-background', 'white', '-flatten',
                     optimized_path], (err, stdout) ->


Display friendlier error messages

Display friendlier error messages

--- src/main/js/worksheet/TableWorksheet.coffee
+++ src/main/js/worksheet/TableWorksheet.coffee
@@ -20,7 +20,11 @@

   dataFromDefAndValues = (defs) ->
     defs.map (d) ->
-      {name: d.name, formula: d.definition.expr.text, value: d.value}
+      {name: d.name, formula: d.definition.expr.text, value: displayValue(d.value)}
+
+  errorText = (error) -> "Error in formula on line #{error.line} at position #{error.columnInExpr}"
+  displayValue = (v) -> if v instanceof Error then errorText(v) else v
+

   emptyRow = () -> {name: null, formula: null, value: null}


js state machine

js state machine

--- src/State.coffee
+++ src/State.coffee
@@ -166,7 +166,6 @@

 Machine = (name, object, options, block) ->
   events = EventCollection()
-  #states = StateCollection()
   states = []

   callbacks = CallbackCollection()
@@ -184,7 +183,7 @@
   add_methods_to_object: (name, object) ->
     object[name] = self.state()
     object[name+'_events'] = events.all()
-    object[name+'_states'] = states.all()
+    object[name+'_states'] = states

   add_event_methods: (name, object, event) ->
     object[name] = -> event.fire(arguments)


Delay and sound timers are delayed as they should

--- src/chip8.coffee
+++ src/chip8.coffee
@@ -112,6 +112,9 @@
     Y = instructionLo >> 4
     instruction = (instructionHi << 8) | instructionLo

+    delayTimer-- if delayTimer > 0
+    soundTimer-- if soundTimer > 0
+
     programCounter += 2
     switch 0xF0 & instructionHi
       when 0x00


test(view): properly test the service

test(view): properly test the service

--- test/spec/services/view.coffee
+++ test/spec/services/view.coffee
@@ -18,138 +18,121 @@
         data:
           transition: 'slide'
       .state 'fourth',
-        url: 'home/foo/bar/fourth'
+        url: '/home/foo/bar/fourth'
         data:
           transition: 'slide'
       .state 'fifth',
-        url: 'home/baz/fifth'
+        url: '/home/baz/fifth'
         data:
           transition: 'slide'
-
-    null
+    return

   viewService = null
   state = null
+  scope = null

-  beforeEach inject (bpViewService, $state) ->
+  home = second = third = fourth = fifth = null
+
+
+  beforeEach inject ($rootScope, bpViewService, $state) ->
+    scope = $rootScope.$new()
     viewService = bpViewService
     state = $state
+    home = state.get 'home'
+    second = state.get 'second'
+    third = state.get 'third'
+    fourth = state.get 'fourth'
+    fifth = state.get 'fifth'
+
+  describe 'construction', ->
+    it 'should be initted', ->
+      expect(viewService.transition).toBe null
+      expect(viewService.lastTransition).toBe null
+
+  describe 'listen', ->
+    it 'should listen to events', ->
+      spyOn viewService, 'onStateChangeStart'
+      spyOn viewService, 'onViewContentLoaded'
+
+      viewService.listen()
+
+      scope.$emit '$stateChangeStart'
+      scope.$emit '$viewContentLoaded'
+
+      expect(viewService.onStateChangeStart).toHaveBeenCalled()
+      expect(viewService.onViewContentLoaded).toHaveBeenCalled()
+
+  describe 'onStateChangeStart', ->
+    it 'should set transition', ->
+      viewService.onStateChangeStart {}, {},
+        direction: 'foo'
+        transition: 'bar'
+      expect(viewService.transition).toBe 'bar-foo'
+
+      viewService.onStateChangeStart {}, second, {}, home
+      expect(viewService.transition).toBe 'slide-normal'
+
+  describe 'onViewContentLoaded', ->
+    it 'should add and remove classes', ->
+      $views = angular.element '<div ui-view>'
+      angular.element('body').append $views
+      scope.$apply()
+
+      viewService.transition = 'foo'
+      viewService.onViewContentLoaded()
+
+      expect($views.hasClass 'foo').toBe true
+
+      viewService.transition = 'bar'
+      viewService.onViewContentLoaded()
+
+      expect($views.hasClass 'foo').toBe false
+      expect($views.hasClass 'bar').toBe true
+
+      viewService.transition = null
+      viewService.onViewContentLoaded()
+
+      expect($views.hasClass 'foo').toBe false
+      expect($views.hasClass 'bar').toBe false
+
+  describe 'setTransition', ->
+    it 'should set transition', ->
+      viewService.setTransition 'foo', 'bar'
+      expect(viewService.transition).toBe 'foo-bar'
+
+      viewService.setTransition null, 'bar'
+      expect(viewService.transition).toBe null
+
+      viewService.setTransition 'foo', null
+      expect(viewService.transition).toBe null

   describe 'getDirection', ->
+    it 'should detect "normal"', ->
+      expect(viewService.getDirection home, second).toBe 'normal'
+      expect(viewService.getDirection fourth, fifth).toBe 'normal'

-    # using state names
-    it 'should detect normal direction using state name strings', ->
-      expect(viewService.getDirection 'home', 'second').toBe 'normal'
+    it 'should detect "reverse"', ->
+      expect(viewService.getDirection second, home).toBe 'reverse'

-    it 'should detect reverse direction using state name strings', ->
-      expect(viewService.getDirection 'second', 'home').toBe 'reverse'
+    it 'should detect no direction', ->
+      expect(viewService.getDirection third, fifth).toBe null
+      expect(viewService.getDirection {url: '^'}).toBe null

-    it 'should detect normal direction using state names in properties', ->
-      expect(viewService.getDirection
-        from: 'home'
-        to: 'second'
-      ).toBe 'normal'
+  describe 'getType', ->
+    it 'should detect "normal" type', ->
+      expect(viewService.getType home, second, 'normal').toBe 'slide'

-    it 'should detect reverse direction using state name properties', ->
-      expect(viewService.getDirection
-        from: 'second'
-        to: 'home'
-      ).toBe 'reverse'
+    it 'should detect "reverse" type', ->
+      expect(viewService.getType home, second, 'reverse').toBe 'fade'

-    it 'should detect none direction from "^" url', ->
-      state.transitionTo 'home'
-      expect(viewService.getDirection
-        to: 'second'
-      ).toBe ''
+    it 'should detect no type', ->
+      expect(viewService.getType {}, {}, 'reverse').toBe null
+      expect(viewService.getType {}, {}, 'normal').toBe null

-    it 'should detect reverse direction using state objects', ->
-      expect(viewService.getDirection
-        name: 'second'
-        url: '/home/second'
-      ,
-        url: '/home'
-        name: 'home'
-      ).toBe('reverse')
-
-    it 'should detect normal direction using state objects without urls', ->
-      expect(viewService.getDirection
-        name: 'second'
-      ,
-        name: 'home'
-      ).toBe('reverse')
-
-    it 'should detect normal direction for diverging paths', ->
-      expect(viewService.getDirection 'fourth', 'fifth').toBe 'normal'
-
-    # using urls
-    it 'should detect normal direction using url strings', ->
-      expect(viewService.getDirection '/home', '/home/second').toBe 'normal'
-
-    it 'should detect reverse direction using url strings', ->
-      expect(viewService.getDirection '/home/second', '/home').toBe 'reverse'
-
-    it 'should detect normal direction using urls in properties', ->
-      expect(viewService.getDirection
-        from: '/home'
-        to: '/home/second'
-      ).toBe 'normal'
-
-    it 'should detect reverse direction using urls in properties', ->
-      expect(viewService.getDirection
-        from: '/home/second'
-        to: '/home'
-      ).toBe 'reverse'
-
-    it 'should detect none direction using "equal" URLs', ->
-      expect(
-        viewService.getDirection '/home/third', '/home/second'
-      ).toBe ''
-
-    it 'should detect none direction from "^" url', ->
-      state.transitionTo 'home'
-      expect(viewService.getDirection
-        to: '/second'
-      ).toBe ''
-
-    it 'should not care about trailing slashes', ->
-      expect(viewService.getDirection '/home/', '/home/second').toBe 'normal'
-      expect(viewService.getDirection '/home', '/home/second/').toBe 'normal'
-      expect(viewService.getDirection '/home/', '/home/second/').toBe 'normal'
-
-    # back button detection edge case
-    it 'should detect reverse direction in paramzartized URLs (1)', ->
-      expect(viewService.getDirection
-        from: 'third'
-        to: 'second'
-      ).toBe 'reverse'
-
-    it 'should detect reverse direction in paramzartized URLs (2)', ->
-      expect(viewService.getDirection
-        from: '/home/second/:third'
-        to: 'second'
-      ).toBe 'reverse'
-
-    # jumping levels
-    it 'should detect reverse directions when jumping back to /home', ->
-      expect(viewService.getDirection
-        from: '/home/second/:third'
-        to: '/home'
-      ).toBe 'reverse'
-
-    it 'should detect reverse directions when jumping back to state home', ->
-      expect(viewService.getDirection
-        from: 'third'
-        to: 'home'
-      ).toBe 'reverse'
-
-    it 'should detect normal directions when jumping from home to third', ->
-      expect(viewService.getDirection
-        from: 'home'
-        to: 'third'
-      ).toBe 'normal'
-
-    it 'should detect normal directions when jumping from /home to third', ->
-      expect(viewService.getDirection
-        from: '/home'
-        to: '/home/second/:third'
-      ).toBe 'normal'
+  describe '_getURLSegments', ->
+    it 'should return correct array', ->
+      fn = viewService._getURLSegments
+      expect(fn home)  .toEqual ['', 'home']
+      expect(fn second).toEqual ['', 'home', 'second']
+      expect(fn third) .toEqual ['', 'home', 'second', ':third']
+      expect(fn {})    .toEqual ['']


parameter tests

parameter tests

--- test/unit/builder_tests.coffee
+++ test/unit/builder_tests.coffee
@@ -44,3 +44,18 @@
       me = unit.makeFunction 'getPointer', i8
       answer = me.value i32, 42
       (-> me.return answer).should.throw /type/i
+
+  describe 'parameter', ->
+    it 'expects a parameter index', ->
+      me = unit.makeFunction 'nothing'
+      (-> me.parameter()).should.throw /index/i
+      (-> me.parameter 'foobar').should.throw /index/i
+
+    it 'expects a valid parameter index', ->
+      me = unit.makeFunction 'onething', llvm.getVoidTy(), i32
+      (-> me.parameter 1).should.throw /index/i
+
+    it 'produces a parameter value', ->
+      me = unit.makeFunction 'it', llvm.getVoidTy(), i32
+      param = me.parameter 0
+      param.type.toString().should.equal 'i32'


flush documents to mongo on incremental compiles

--- services/web/app/coffee/Features/Compile/ClsiManager.coffee
+++ services/web/app/coffee/Features/Compile/ClsiManager.coffee
@@ -174,9 +174,14 @@
 			ClsiManager._finaliseRequest project_id, options, project, docs, files, callback

 	_getContentFromDocUpdater: (project_id, callback = (error, docs) ->) ->
-		DocumentUpdaterHandler.getProjectDocs project_id, (error, docs) ->
-			return callback(error) if error?
-			callback(null, docs)
+		# Workaround: for now, always flush project to mongo on compile
+		# until we have automatic periodic flushing on the docupdater
+		# side, to prevent documents staying in redis too long.
+		DocumentUpdaterHandler.flushProjectToMongo project_id, (error) ->
+			return callback(error) if error?
+			DocumentUpdaterHandler.getProjectDocs project_id, (error, docs) ->
+				return callback(error) if error?
+				callback(null, docs)

 	_getContentFromMongo: (project_id, callback = (error, docs, files) ->) ->
 		DocumentUpdaterHandler.flushProjectToMongo project_id, (error) ->


add ol-style.css to fingerprint list

add ol-style.css to fingerprint list

--- services/web/app/coffee/infrastructure/ExpressLocals.coffee
+++ services/web/app/coffee/infrastructure/ExpressLocals.coffee
@@ -45,6 +45,7 @@
 	["#{jsPath}libs/#{pdfjs}/pdf.worker.js"]
 	["#{jsPath}libs/#{pdfjs}/compatibility.js"]
 	["/stylesheets/style.css"]
+	["/stylesheets/ol-style.css"]
 ]

 for paths in pathList


Fixed test failure in Punch#out

Fixed test failure in Punch#out

--- tests/models/test_punch.coffee
+++ tests/models/test_punch.coffee
@@ -190,8 +190,9 @@
   describe '#out(punch)', ->
     beforeEach ->
       zone = 'America/New_York'
-      start = moment.tz({hour: 7}, zone)
-      end = moment.tz({hour: 18}, zone)
+      now = moment.tz zone
+      start = moment.tz({year: now.year(), month: now.month(), day: now.date(), hour: 7}, zone)
+      end = moment.tz({year: now.year(), month: now.month(), day: now.date(), hour: 18}, zone)
       timetable = new Timetable(start, end, zone)
       timetable.setVacation(13, 0)
       timetable.setSick(5, 0)


Add more pain for small screens.

Add more pain for small screens.

--- src/docco.coffee
+++ src/docco.coffee
@@ -84,12 +84,8 @@
   save = (docsText, codeText) ->
     sections.push {docsText, codeText}

-  isComment = (line) ->
-    line.match(language.commentMatcher) and
-    not line.match(language.commentFilter)
-
   for line in lines
-    if isComment line
+    if line.match(language.commentMatcher) and not line.match(language.commentFilter)
       if hasCode
         save docsText, codeText
         hasCode = docsText = codeText = ''


Removes listenerCount

Removes listenerCount

--- src/dredd.coffee
+++ src/dredd.coffee
@@ -49,7 +49,7 @@
         @transactionsComplete(config.emitter, stats, callback)

   transactionsComplete: (emitter, stats, callback) ->
-    reporterCount = EventEmitter.listenerCount(emitter, 'end')
+    reporterCount = emitter.listeners('end').length
     emitter.emit 'end' , () ->
       reporterCount--
       if reporterCount is 0


#148: track moves on svg

#148: track moves on svg

--- src/graph.coffee
+++ src/graph.coffee
@@ -182,6 +182,8 @@
       svg = d3.select(container).append('svg')
           .attr('width', width + margin.left + margin.right)
           .attr('height', height + margin.top + margin.bottom)
+          .on('mousemove', (d, i) -> mouse_moves.push d3.mouse g.node())
+
       g = svg
         .append("g")
           .attr("transform", "translate(#{margin.left},#{margin.top})")
@@ -222,13 +224,6 @@
         legend.selectAll('.crosshair-value')
           .data(target_values)
           .text((d) -> d.value)
-
-      g.append('rect')
-        .attr('width', width)
-        .attr('height', height)
-        .attr('fill', 'none')
-        .attr('pointer-events', 'all')
-        .on('mousemove', (d, i) -> mouse_moves.push d3.mouse @)

       target = g.selectAll('.target')
           .data(targets)


Use URL from the variable

Use URL from the variable

--- components/EnsureLocalRuntimes.coffee
+++ components/EnsureLocalRuntimes.coffee
@@ -7,14 +7,12 @@
   for runtime in runtimes
     # Check that we don't have the iframe runtime already
     if runtime.protocol is 'iframe' and runtime.address is iframeAddress
-      # Update 'last seen' property
-      runtime.seen = new Date
       return runtime
   iframeRuntime =
     label: 'NoFlo HTML5 environment'
     id: uuid()
     protocol: 'iframe'
-    address: 'https://noflojs.org/noflo-browser/everything.html?fbp_noload=true&fbp_protocol=iframe'
+    address: iframeAddress
     type: 'noflo-browser'
     seen: Date.now()
   return iframeRuntime


try again

try again

--- angular/test/protractor.coffee
+++ angular/test/protractor.coffee
@@ -26,7 +26,7 @@
   #
   onPrepare: ->
     jasmine.getEnv().addReporter(reporter);
-    browser.driver.manage().window().setSize(1680, 1024)
+    browser.driver.manage().window().setSize(1680, 1624)
   #
   afterLaunch: (exitCode) ->
     new Promise (resolve) ->


back to direct connect

back to direct connect

--- angular/test/protractor.coffee
+++ angular/test/protractor.coffee
@@ -10,8 +10,8 @@
   # seleniumServerJar: '../node_modules/webdriver-manager/selenium/selenium-server-standalone-3.3.0.jar'
   # seleniumAddress: 'http://localhost:4444/wd/hub'
   allScriptsTimeout: 20000
-  # directConnect: true
-  seleniumAddress: 'http://localhost:4444/wd/hub'
+  directConnect: true
+  # seleniumAddress: 'http://localhost:4444/wd/hub'
   capabilities:
     browserName:     'firefox'
   baseUrl:           'http://localhost:3000'


CollaborativeEditorPane: Make createEditor async

--- client/landing/CollaborativeWorkspace/panes/collaborativeeditorpane.coffee
+++ client/landing/CollaborativeWorkspace/panes/collaborativeeditorpane.coffee
@@ -7,22 +7,24 @@
     super options, data

     @container.on "viewAppended", =>
-      @createEditor()
-      @ref        = @workspace.firebaseRef.child @sessionKey
-      @firepad    = Firepad.fromCodeMirror @ref, @codeMirrorEditor
+      @createEditor =>
+        @ref        = @workspace.firebaseRef.child @sessionKey
+        @firepad    = Firepad.fromCodeMirror @ref, @codeMirrorEditor

-      @firepad.on "ready", =>
-        @firepad.setText " " if @firepad.isHistoryEmpty() # fix for a firepad bug
-        @codeMirrorEditor.scrollTo 0, 0
-        {file, content} = @getOptions()
-        return @openFile file, content  if file
+        @firepad.on "ready", =>
+          if @firepad.isHistoryEmpty() # fix for a firepad bug
+            @firepad.setText " "
+            @firepad.setText ""
+          @codeMirrorEditor.scrollTo 0, 0
+          {file, content} = @getOptions()
+          return @openFile file, content  if file

-      if @amIHost
-        @ref.on "value", (snapshot) =>
-          value = @workspace.reviveSnapshot snapshot
-          return unless value
-          if value.WaitingSaveRequest is yes
-            return @save()
+        if @amIHost
+          @ref.on "value", (snapshot) =>
+            value = @workspace.reviveSnapshot snapshot
+            return unless value
+            if value.WaitingSaveRequest is yes
+              return @save()

   openFile: (file, content) ->
     @setData file
@@ -60,7 +62,8 @@
   setValue: (value) ->
     @codeMirrorEditor.setValue value

-  createEditor: ->
+  createEditor: (callback)->
+
     @codeMirrorEditor = CodeMirror @container.getDomElement()[0],
       lineNumbers     : yes
       scrollPastEnd   : yes
@@ -71,6 +74,8 @@

     @setEditorTheme()
     @setEditorMode 'html'
+
+    callback?()

   handleSave: ->
     @save()


activity: suggestions documentation fix

activity: suggestions documentation fix

--- client/activity/lib/flux/stores/suggestions/suggestionsselectedindexstore.coffee
+++ client/activity/lib/flux/stores/suggestions/suggestionsselectedindexstore.coffee
@@ -2,7 +2,7 @@
 KodingFluxStore = require 'app/flux/store'

 ###*
- * Store to contain channels selected index
+ * Store to contain suggestions selected index
 ###
 module.exports = class SuggestionsSelectedIndexStore extends KodingFluxStore


hide follow / unfollow button for guests

--- client/app/Applications/Members.kdapplication/ContentDisplays/profileview.coffee
+++ client/app/Applications/Members.kdapplication/ContentDisplays/profileview.coffee
@@ -28,9 +28,12 @@

     defaultState = if memberData.followee is yes then "Unfollow" else "Follow"

-    @followButton = new MemberFollowToggleButton
-      style           : "kdwhitebtn profilefollowbtn"
-    , memberData
+    if memberData.profile.nickname is KD.whoami().profile.nickname or KD.whoami().type is 'unregistered'
+      @followButton = new KDView
+    else
+      @followButton MemberFollowToggleButton
+        style      : "kdwhitebtn profilefollowbtn"
+      , memberData

     @skillTags = @putSkillTags()


Uncommented out the weird tests that caused conflicts with other tests. Previous re-factorings solved the issue.

--- spec/barcode_input_spec.coffee
+++ spec/barcode_input_spec.coffee
@@ -131,23 +131,25 @@

       it 'should detect them', -> expect( @event ).toHaveBeenTriggeredOn( bc_input )

-    # TODO: This test cause later tests to pass when they should fail...
-    # describe 'triggered on non-barcode inputs', ->
-    #   beforeEach ->
-    #     spyOnEvent @input, @event
-
-    #     press_key '0', on:document.getElementById('nonInput')
-
-    #   it 'should not detect them', -> expect( @event ).not.toHaveBeenTriggeredOn( @input )
-
-    # TODO: This test cause later tests to pass when they should fail...
-    # describe 'triggered on non-barcode textareas', ->
-    #   beforeEach ->
-    #     spyOnEvent @input, @event
-
-    #     press_key '0', on:document.getElementById('textarea')
-
-    #   it 'should not detect them', -> expect( @event ).not.toHaveBeenTriggeredOn( @input )
+    describe 'triggered on non-barcode inputs', ->
+      beforeEach ->
+        spyOnEvent bc_input, @event
+
+        press_key '0', on:document.getElementById('nonInput')
+
+        waits assert_delay
+
+      it 'should not detect them', -> expect( @event ).not.toHaveBeenTriggeredOn( bc_input )
+
+    describe 'triggered on non-barcode textareas', ->
+      beforeEach ->
+        spyOnEvent bc_input, @event
+
+        press_key '0', on:document.getElementById('textarea')
+
+        waits assert_delay
+
+      it 'should not detect them', -> expect( @event ).not.toHaveBeenTriggeredOn( bc_input )

@@ -189,7 +191,6 @@

-  # If made to fail, this test can pass if the previous not.toHaveBeenTriggered tests are enabled...
   describe 'Number Pad Keys', ->
     beforeEach ->
       spyOnEvent bc_input, 'input.barcode'


Add more tests to Canadian phones

Add more tests to Canadian phones

--- spec/countries/CAN-spec.coffee
+++ spec/countries/CAN-spec.coffee
@@ -8,7 +8,10 @@

 		number = ''

-		afterEach ->
+		it 'a number', ->
+			# Arrange
+			number = "+1 204 9898656"
+
 			# Act
 			result = Phone.getPhoneInternational(number)

@@ -16,9 +19,17 @@
 			expect(result.valid).to.be.true
 			expect(result.countryNameAbbr).to.equal('CAN')

-		it 'a number', ->
+		it 'a national number', ->
 			# Arrange
-			number = "+1 204 9898656"
+			number = "(204) 123 1234"
+
+			# Act
+			result = Phone.getPhoneNational(number, '1')
+
+			# Assert
+			expect(result.valid).to.be.true
+			expect(result.countryNameAbbr).to.equal('CAN')
+

 	describe 'Should validate a', ->


Fix packagepanel tests

Fix packagepanel tests

--- spec/package-panel-spec.coffee
+++ spec/package-panel-spec.coffee
@@ -93,13 +93,13 @@
       it "adds the package name to the disabled packages array", ->
         p2View = panel.installedPackages.find("[name='p2']").view()
         p2View.enableToggle.find('a').click()
-        expect(configObserver).toHaveBeenCalledWith(['p1', 'p3', 'p2'])
+        expect(configObserver).toHaveBeenCalledWith(['p1', 'p3', 'p2'], previous: ['p1', 'p3'])

     describe "when the enable link is clicked", ->
       it "removes the package name from the disabled packages array", ->
         p3View = panel.installedPackages.find("[name='p3']").view()
         p3View.enableToggle.find('a').click()
-        expect(configObserver).toHaveBeenCalledWith(['p1'])
+        expect(configObserver).toHaveBeenCalledWith(['p1'], previous: ['p1', 'p3'])

     describe "when Uninstall is clicked", ->
       describe "when the uninstall succeeds", ->


disable scrolltop

disable scrolltop

--- src/index.coffee
+++ src/index.coffee
@@ -88,7 +88,7 @@
   self.closeCls = self.opts.closeCls or self.closeCls

   # scroll to top before opening modal
-  win.scrollTo 0, 0
+  # win.scrollTo 0, 0

   # make sure nothing interfere to the visibility of this element
   self.elWrapper.style.display = self.elWrapper.style.visibility = ""


expose common helpers in public api

expose common helpers in public api

--- src/index.coffee
+++ src/index.coffee
@@ -1,11 +1,12 @@
-module.exports =
-  ccw: require "./ccw"
-  fit: require "./fit"
-  Transform: require "./transform"
-  trees: require "./trees"
-  skin:  require "./skin"
-  intersect:  require "./intersect"
-  perpendicular: require "./perpendicular"
-  trapezoid: require "./trapezoid"
-  skeleton: require "./skeleton"
+C = require "./common"
+C.ccw = require "./ccw"
+C.fit = require "./fit"
+C.Transform = require "./transform"
+C.trees = require "./trees"
+C.skin =  require "./skin"
+C.intersect =  require "./intersect"
+C.perpendicular = require "./perpendicular"
+C.trapezoid = require "./trapezoid"
+C.skeleton = require "./skeleton"

+module.exports = C


Spaces optional in front of quoted strings


Log deferred tasks only if present

Log deferred tasks only if present

--- src/index.coffee
+++ src/index.coffee
@@ -235,7 +235,8 @@
         for match in matches
           tasks = tasks.concat match.tasks

-      @log.info "Deferred Tasks '#{tasks}'"
+      if tasks.length > 0
+        @log.info "Deferred Tasks '#{tasks}'"
       return callback()

     @_executingTasks = true


Default headers to 700 weight

Default headers to 700 weight

--- src/index.coffee
+++ src/index.coffee
@@ -18,7 +18,7 @@
     headerGrayHue: 0
     bodyGray: 20
     bodyGrayHue: 0
-    headerWeight: 400
+    headerWeight: 700
     bodyWeight: 400
     boldWeight: 700
     fontFaces: []


Fix vertical value emitting

Fix vertical value emitting

--- framer/Components/RangedSliderComponent.coffee
+++ framer/Components/RangedSliderComponent.coffee
@@ -119,7 +119,15 @@
 				@leftKnob.draggable._touchStart(event)

 		else
-			@value = @valueForPoint(Events.touchEvent(event).clientY - @screenScaledFrame().y) / @canvasScaleY() - offsetY
+			clickedValue = @valueForPoint(Events.touchEvent(event).clientY - @screenScaledFrame().y) / @canvasScaleY() - offsetY
+
+			if clickedValue > @maxValue
+				@maxValue = clickedValue
+				@rightKnob.draggable._touchStart(event)
+
+			if clickedValue < @minValue
+				@minValue = clickedValue
+				@leftKnob.draggable._touchStart(event)

 		@_updateValue()

@@ -142,11 +150,10 @@

 		else
 			@fill.y = @leftKnob.midY
-			@fill.height =  @rightKnob.midY - @leftKnob.midY
+			@fill.height = @rightKnob.midY - @leftKnob.midY

 	_updateKnob: =>
 		if @width > @height
-
 			@leftKnob.midX = @fill.x
 			@leftKnob.centerY()

@@ -185,13 +192,13 @@

 		if @width > @height
 			@fill.height = @height
-
 			@leftKnob.midX = @pointForValue(@minValue)
 			@rightKnob.midX = @pointForValue(@maxValue)
 			@leftKnob.centerY()
 		else
 			@fill.width = @width
-			@leftKnob.midY = @pointForValue(@value)
+			@leftKnob.midY = @pointForValue(@minValue)
+			@rightKnob.midY = @pointForValue(@maxValue)
 			@leftKnob.centerX()

 		if @width > @height
@@ -233,7 +240,6 @@
 				@sliderOverlay.width = @hitArea
 				@sliderOverlay.height = @height + @hitArea

-
 	@define "min",
 		get: -> @_min or 0
 		set: (value) -> @_min = value if _.isFinite(value)
@@ -273,25 +279,16 @@

 	_knobDidMove: =>
 		if @width > @height
-
 			@minValue = @valueForPoint(@leftKnob.midX)
 			@maxValue = @valueForPoint(@rightKnob.midX)
 		else
-			@value = @valueForPoint(@leftKnob.midY)
+			@minValue = @valueForPoint(@leftKnob.midY)
+			@maxValue = @valueForPoint(@rightKnob.midY)

 	_updateValue: =>
-
-		# return if @_lastUpdatedMinValue is @minValue or @_lastUpdatedMaxValue is @maxValue
-		#
-		# @_lastUpdatedMinValue = @minValue
-		# @_lastUpdatedMaxValue = @maxValue
-
-		# @_range = [@minValue, @maxValue]
-
 		@emit(Events.SliderValueChange)
 		@emit(Events.SliderMinValueChange, @minValue)
 		@emit(Events.SliderMaxValueChange, @maxValue)
-

 	pointForValue: (value) ->
 		for knob in [@leftKnob, @rightKnob]
@@ -336,6 +333,7 @@
 			animationOptions.properties = {y: @pointForValue(value) - (@rightKnob.height/2)}

 		@rightKnob.animate(animationOptions)
+
 	##############################################################
 	## EVENT HELPERS


fix ie old version String index bug

fix ie old version String index bug

fix ie old version String index bug

--- src/jsonp.coffee
+++ src/jsonp.coffee
@@ -66,7 +66,7 @@

 randomString = (length) ->
     str = ''
-    str += random().toString(36)[2] while str.length < length
+    str += random().toString(36).slice(2, 3) while str.length < length
     return str

 objectToURI = (obj) ->


show a log in console if grabBackFocus works

--- content_scripts/vimium_frontend.coffee
+++ content_scripts/vimium_frontend.coffee
@@ -34,6 +34,7 @@
 # If an input grabs the focus before the user has interacted with the page, then grab it back (if the
 # grabBackFocus option is set).
 class GrabBackFocus extends Mode
+  logged: false

   constructor: ->
     exitEventHandler = =>
@@ -73,6 +74,9 @@

   grabBackFocus: (element) ->
     return @continueBubbling unless DomUtils.isFocusable element
+    unless @logged or element == document.body
+      @logged = true
+      console.log "An auto-focusing action is blocked by Vimium"
     element.blur()
     @suppressEvent


Add specs for stylus failures

Add specs for stylus failures

--- test/units/config/initializers/templates/stylus.spec.coffee
+++ test/units/config/initializers/templates/stylus.spec.coffee
@@ -17,24 +17,20 @@
         @stylusRenderCalled = true
         cb? null, 'irrelevant'

-  afterEach ->
-    require.cache[@stylusPath].exports = @safeStylus
-
+  afterEach -> require.cache[@stylusPath].exports = @safeStylus

   it 'should exist', ->
     expect(initializer).toBeDefined()

   describe 'when called', ->
-    beforeEach ->
-      initializer @config
+    beforeEach -> initializer @config

     it 'should have added the stylus template engine to the config', ->
       expect(@config.engines.templates.stylus).toBeDefined()
       expect(@config.engines.templates.stylus.render).toBeDefined()

     describe 'the defined render method', ->
-      subject 'render', ->
-        @config.engines.templates.stylus.render
+      subject 'render', -> @config.engines.templates.stylus.render

       describe 'when called', ->
         it 'should have called the stylus render method', ->
@@ -42,4 +38,19 @@
           expect(@stylusRenderCalled).toBeTruthy()
           expect(result).toBe('irrelevant')

+  describe 'when the stylus render fails', ->
+    beforeEach ->
+      @stylusRenderCalled = false
+      @safeStylus = require.cache[@stylusPath].exports
+      require.cache[@stylusPath].exports = (tpl) =>
+        render: (cb) => cb? new Error('irrelevant')

+      initializer @config
+
+    subject 'render', -> @config.engines.templates.stylus.render
+
+    afterEach -> require.cache[@stylusPath].exports = @safeStylus
+
+    it 'should have raised an error', ->
+      expect(-> @render 'foo').toThrow()
+


webapp/asisstant: temporary hotfix for creating a log entry

--- src/smc-webapp/assistant/store.coffee
+++ src/smc-webapp/assistant/store.coffee
@@ -87,9 +87,13 @@
     log: () ->
         lang  = @get('lang')
         path  = @get('path')
-        c0    = @get_category_list0()[@get('category0')]
-        c1    = @get_category_list1()[@get('category1')]
-        c2    = @get_category_list2()[@get('category2')]
+        category0 = @get('category0')
+        category1 = @get('category1')
+        category2 = @get('category2')
+        return if (not category0?) or (not category1?) or (not category2?)
+        c0    = @get_category_list0()[category0]
+        c1    = @get_category_list1()[category1]
+        c2    = @get_category_list2()[category2]
         entry = [c0, c1, c2]
         project_actions = redux.getProjectActions(@get('project_id'))
         project_actions.log


remove atom config file

remove atom config file

--- src/symlinks/atom.symlink/config.cson
+++ src/symlinks/atom.symlink/config.cson
@@ -1,29 +0,0 @@
-"*":
-  "color-picker":
-    abbreviateValues: true
-  core:
-    closeDeletedFileTabs: true
-    openEmptyEditorOnStart: false
-    telemetryConsent: "limited"
-    themes: [
-      "seti-ui"
-      "seti-syntax"
-    ]
-  editor:
-    fontSize: 30
-    showIndentGuide: true
-    showInvisibles: true
-  "exception-reporting":
-    userId: "3e2a4611-d268-ebf6-e196-335e0474affa"
-  "linter-ui-default":
-    panelHeight: 36
-  pigments:
-    extendAutocompleteToColorValue: true
-    extendAutocompleteToVariables: true
-  "tree-view":
-    autoReveal: true
-    focusOnReveal: false
-  "webbox-color":
-    fillColorAsBackground: false
-  welcome:
-    showOnStartup: false


Fix keymap for windows.

Fix keymap for windows.

--- keymaps/existdb.cson
+++ keymaps/existdb.cson
@@ -14,13 +14,13 @@
     'cmd-alt-g': 'existdb:goto-definition'

 '.platform-linux atom-workspace':
-    'ctrl-enter': 'existdb:run'
-    'ctrl-alt-r': 'existdb:file-symbols'
+    'alt-shift-enter': 'existdb:run'
+    'ctrl-shift-r': 'existdb:file-symbols'
     'ctrl-alt-e': 'existdb:toggle-tree-view'
     'ctrl-alt-g': 'existdb:goto-definition'

-'.platform-windows atom-workspace':
-    'ctrl-enter': 'existdb:run'
-    'ctrl-alt-r': 'existdb:file-symbols'
+'.platform-win32 atom-workspace':
+    'alt-shift-enter': 'existdb:run'
+    'ctrl-shift-r': 'existdb:file-symbols'
     'ctrl-alt-e': 'existdb:toggle-tree-view'
     'ctrl-alt-g': 'existdb:goto-definition'


Relative path, not absolute one

Relative path, not absolute one

--- webapp/app/scripts/main.coffee
+++ webapp/app/scripts/main.coffee
@@ -18,7 +18,7 @@
     districts: database.districts
     candidates: database.candidates
     posts: database.posts
-    urlPrefix: '/cached-donnees.electionsmunicipales.gouv.qc.ca'
+    urlPrefix: 'cached-donnees.electionsmunicipales.gouv.qc.ca'

   state = new QME.Models.State({}, router: router, districtIdFinder: districtIdFinder)


Removed route logging

Removed route logging

--- client/app/router.coffee
+++ client/app/router.coffee
@@ -11,12 +11,10 @@
         'list'                : 'alarmsList'

     calendar: ->
-        console.log 'route:calendar'
         @displayView CalendarView, app.alarms
         @handleFetch()

     alarmsList: ->
-        console.log 'route:alarmsList'
         @displayView ListView, app.alarms
         @handleFetch()


ttest_power: use viewBox to better handle varying screens

--- ttest_power/ttest_power.coffee
+++ ttest_power/ttest_power.coffee
@@ -87,6 +87,10 @@
         .append("svg")
         .attr("width", width)
         .attr("height", height)
+svg.attr("viewBox", "0,0,#{width},#{height}")
+   .style("height", window.innerHeight-20)
+   .style("width", window.innerWidth-420)
+   .attr("preserveAspectRatio", "xMinYMin meet")

 figs = [null, null, null]
 titles = ["Population distributions",


FIx invalid require

FIx invalid require

--- test/digweb_test.coffee
+++ test/digweb_test.coffee
@@ -10,7 +10,7 @@
       respond: sinon.spy()
       hear: sinon.spy()

-    require('../src/dnsimple')(@robot)
+    require('../src/digweb')(@robot)

   it 'registers a hear listener for "dig (.*)"', ->
     expect(@robot.hear).to.have.been.calledWith(/dig (.*)$/i)


added method _createConnection

added method _createConnection

--- modules/dnode/lib/Bridge.coffee
+++ modules/dnode/lib/Bridge.coffee
@@ -2,11 +2,11 @@
 shoe = require('shoe')
 dnode = require('dnode')

-module.exports =
+module.exports =

     class DNodeBridge extends floyd.Context
-
-
+
+
         ##
         ##
         ##
@@ -15,141 +15,145 @@
             ##
             ##
             config = super new floyd.Config
-
+
                 data:
                     parent: false
                     ports: []
                     gateways: (if floyd.system.platform is 'remote' then ['remote'] else [])
                     route: '/dnode'

-            , config
-
+            , config
+
             if config.data.parent
                 config.data.ports.push parent: true
-
-            ## hack to delegate lookups to origin
-
+
+            ## hack to delegate lookups to origin
+
             if origin = config.ORIGIN
-
+
                 floyd.tools.objects.intercept @, 'lookup', (name, identity, fn, lookup)=>
-
+
                     #console.log 'lookup', name
-
-                    lookup name, identity, (err, ctx)=>
+
+                    lookup name, identity, (err, ctx)=>
                         if ctx
                             fn(null, ctx)
-
+
                         else
                             #console.log 'retry lookup', origin+'.'+name, err.message
                             lookup origin+'.'+name, identity, fn
-
-
-
-            ## hack to connect us before our root-parent gets booted
+
+
+
+            ## hack to connect us before our root-parent gets booted
             _parent = _parent.parent while (_parent ?= @parent || @).parent
             floyd.tools.objects.intercept _parent, 'boot', (done, boot)=>
                 @_connect (err)=>
                     return done(err) if err
-
+
                     boot (err)=>
-                        return done(err) if err
-
+                        return done(err) if err
+
                         @_listen done

-            return config
-
-
+            return config
+
+
         ##
         ##
         ##
         _connect: (fn)->
             done = false

-            @_process @data.gateways,
-
+            @_process @data.gateways,
+
                 #done: fn
-
-                ## --> TEMPORARY DEBUGGING remove the following after it is made
+
+                ## --> TEMPORARY DEBUGGING remove the following after it is made
                 ##     absolutely clear that it never gets called twice without an error.
-                ##
+                ##
                 ##     don't forget to comment in the above done: fn (!!!)
                 ##
                 ##     its also save to remove the done = false statement at the beginning of _connect
                 ##
                 done: (err)=>
                     return fn(err) if err
-
+
                     if !done && ( done = true )
                         fn()
-
-                    else
-                        console.log 'doppelt!'
-                ## <-- TEMPORARY DEBUGGING
-
-                each: (conf, next)=>
-
+
+                    else
+                        console.log 'doppelt!'
+                ## <-- TEMPORARY DEBUGGING
+
+                each: (conf, next)=>
+
                     reconn = require('reconnect-core') ()=>
                         if conf.tls
                             @logger.info 'connecting to tls-gateway:', conf.host||'localhost', conf.port
                         else
                             @logger.info 'connecting to gateway:', conf
-
-                        if conf is 'remote'
-                            c = shoe @data.route
-                        else if conf.tls
-                            c = require('tls').connect conf
-                        else
-                            c = require('net').connect conf
+
+                        c = @_createConnection conf

                         c.on 'error', (err)=>
                             @logger.error err
-
+
                         d = @_createLocal next
                         d.pipe(c).pipe d
-
+
                         return c
-
+
                     reconn().connect()
-
-
-
-
+
+
+        ##
+        ##
+        ##
+        _createConnection: (conf)->
+            if conf is 'remote'
+                shoe @data.route
+            else if conf.tls
+                require('tls').connect conf
+            else
+                require('net').connect conf
+
         ##
         ##
         ##
         _listen: (fn)->
-
-            @_process @data.ports,
-
+
+            @_process @data.ports,
+
                 done: fn
-
+
                 each: (conf, next)=>
                     if conf.tls
                         @logger.info 'listening on tls-port:', conf.host||'localhost', conf.port
                     else
                         @logger.info 'listening on port:', conf
-
+
                     handler = (err)=>
                         fn(err) if err
-
+
                     if conf.parent
-
+
                         parent = @parent
                         while parent && !(server = parent.server) && parent.parent
                             parent = parent.parent
-
+
                         @_createServerSocket parent.server, handler
-
-                    else if conf.child
-
+
+                    else if conf.child
+
                         @_createServerSocket @children[conf.child], handler
-
+
                     else if conf.ctx
-
+
                         @lookup conf.ctx, @identity, (err, ctx)=>

                             @_createServerSocket ctx.server, handler
-
+
                     else if conf.tls
                         require('tls').createServer conf, (c)=>
                             d = @_createLocal(handler)
@@ -158,57 +162,57 @@
                             c.on 'error', handler

                         .listen conf.port, conf.host
-
-                    else
-                        @_createLocal(handler).listen conf
-
+
+                    else
+                        @_createLocal(handler).listen conf
+
                     ##
                     next()
-
+
         ##
         ##
         ##
         _createServerSocket: (server, handler)->
-
+
             sock = shoe (stream)=>
                 d = @_createLocal handler
-
+
                 d.pipe(stream).pipe d
-
+
             sock.install server, @data.route
-
-
-
+
+
+
         ##
         ##
         ##
         _createLocal: (fn)->
-
+
             ##
             root = @parent || @
-
+
             ##
             dnode (proxy, conn)=>
-
+
                 child = null
-
+
                 conn.on 'remote', (remote)=>
-
+
                     if !(child = root.children[remote.id])
                         child = new floyd.dnode.Remote root
-
+
                         child.init (id: remote.id, type:'dnode.Remote'), (err)=>
                             return fn(err) if err
-
+
                         child._useProxy remote
-
+
                         root.children.push child

-                        fn()
-
+                        fn()
+
                     else
                         child._useProxy remote
-
+
                     ##
                     @_emit 'connected',
                         id: child.id
@@ -218,22 +222,18 @@
                         @_emit 'disconnected',
                             id: child.id

-
+
                     ##
                     conn.on 'error', (err)=>
-
+
                         console.log 'conn error!', err
-
-                        fn err
-
-
-
+
+                        fn err
+
+
+
                 ## remote api
                 id: root.id
-
-                lookup: (args...)=>
+
+                lookup: (args...)=>
                     child.lookup.apply child, args
-
-
-
-


share server: fix serving static missing files to be FAST

--- src/smc-hub/share/render-static-path.coffee
+++ src/smc-hub/share/render-static-path.coffee
@@ -1,6 +1,10 @@
 ###
 Render a public path using a the official express static server.
 ###
+
+
+fs = require('fs')
+os_path              = require('path')

 serve_static         = require('serve-static')
 finalhandler         = require('finalhandler')
@@ -14,6 +18,10 @@

 INDEX_OPTIONS =
     icons: true
+
+# NOTE: we never clear these caches.  However, there is at most one for
+# every public_path, so it probably wastes very little memory.  Someday
+# should change to an LRU cache...

 _serve_static_cache = {}
 get_serve_static = (dir) ->
@@ -32,14 +40,23 @@
         dir   : required   # directory on disk containing files for this path
         path  : required

-    s_static = get_serve_static(dir)
-    s_index  = get_serve_index(dir)
-    if path == ''
-        path = '/'
-    req.url  = path
-    s_static req, res, (err) ->
+    # We first test that we have access to the file (and it exists) before
+    # messing with the express static server.  I don't know why, but for some
+    # reason it hangs forever when fed an uknown path, which obviously leads
+    # to a very bad experience for users!
+    target = os_path.join(opts.dir, opts.path)
+    fs.access target, fs.constants.R_OK, (err) ->
         if err
-            finalhandler(err)
-        else
-            s_index(req, res, finalhandler)
+            res.sendStatus(404)
+            return
+        s_static = get_serve_static(dir)
+        s_index  = get_serve_index(dir)
+        if path == ''
+            path = '/'
+        req.url  = path
+        s_static req, res, (err) ->
+            if err
+                finalhandler(err)
+            else
+                s_index(req, res, finalhandler)


Remove JS-applied help block CSS

Remove JS-applied help block CSS

This causes layout issues in most of our use cases (e.g., with
`form-horizontal`) and is difficult to override in the CSS because it's
applied to the element directly.

I think it should just be applied manually in the target application's
CSS in any special cases where it's actually needed.

--- app/cells/select/select.coffee
+++ app/cells/select/select.coffee
@@ -18,14 +18,6 @@

     $this.chosen data

-    # Append help-block to parent .form-group element
-    $formGroup = $this.closest('.form-group')
-    $formGroup.css('position', 'relative')
-    $helpBlock = $formGroup.find('.help-block')
-    $helpBlock.css('position', 'absolute')
-    $helpBlock.css('top', '1.6em')
-    $helpBlock.css('left', '3em')
-
     init_group_selectable($this)

     if data.remoteOptions


Finish registered proccess by invitation

--- app/coffee/modules/auth.coffee
+++ app/coffee/modules/auth.coffee
@@ -298,8 +298,92 @@
     return {link:link}

+    ###################
+    ## Invitation
+    ###################
+
+InvitationDirective = ($auth, $confirm, $location, $params) ->
+    link = ($scope, $el, $attrs) ->
+        token = $params.token
+
+        promise = $auth.getInvitation(token)
+        promise.then (invitation)->
+            $scope.invitation = invitation
+
+        promise.then null, (response) ->
+                $location.path("/login") # TODO: Use the future 'urls' service
+                $confirm.success("Ooops, we have a problems

+                                  Our Oompa Loompas can't find your invitations.") #TODO: i18n
+
+        #$##############
+        # Login form
+        ################
+        $scope.dataLogin = {token: token}
+        loginForm = $el.find("form.login-form").checksley()
+
+        submitLogin = ->
+            if not loginForm.validate()
+                return
+
+            promise = $auth.login($scope.dataLogin)
+            promise.then (response) ->
+                # TODO: finish this. Go tu project home page
+                $location.path("/project/#{$scope.invitation.project_slug}/backlog")
+                $confirm.notify("success", "You've successfully joined to this project",
+                                           "Wellcome to #{$scope.invitation.project_name}")
+
+            promise.then null, (response) ->
+                if response.data._error_message
+                    $confirm.notify("light-error", "According to our Oompa Loompas,
+                                                    your are not registered yet or
+                                                    type an invalid password.") #TODO: i18n
+
+        $el.on "submit", "form.login-form", (event) ->
+            event.preventDefault()
+            submitLogin()
+
+        $el.on "click", "a.button-login", (event) ->
+            event.preventDefault()
+            submitLogin()
+
+        #$##############
+        # Register form
+        #$##############
+        $scope.dataRegister = {token: token}
+        registerForm = $el.find("form.register-form").checksley()
+
+        submitRegister = ->
+            if not registerForm.validate()
+                return
+
+            promise = $auth.register($scope.dataRegister, "private")
+            promise.then (response) ->
+                # TODO: finish this. Go tu project home page
+                $location.path("/project/#{$scope.invitation.project_slug}/backlog")
+                $confirm.notify("success", "You've successfully joined to this project",
+                                           "Wellcome to #{$scope.invitation.project_name}")
+
+            promise.then null, (response) ->
+                if response.data._error_message
+                    $confirm.notify("light-error", "According to our Oompa Loompas,
+                                                    your username/email or password
+                                                    are incorrect.") #TODO: i18n
+
+        $el.on "submit", "form.register-form", (event) ->
+            event.preventDefault()
+            submitRegister
+
+        $el.on "click", "a.button-register", (event) ->
+            event.preventDefault()
+            submitRegister()
+
+    return {link:link}
+
+
 module.directive("tgRegister", ["$tgAuth", "$tgConfirm", RegisterDirective])
 module.directive("tgLogin", ["$tgAuth", "$tgConfirm", "$location", LoginDirective])
 module.directive("tgForgotPassword", ["$tgAuth", "$tgConfirm", "$location", ForgotPasswordDirective])
 module.directive("tgChangePasswordFromRecovery", ["$tgAuth", "$tgConfirm", "$location", "$routeParams",
                                                   ChangePasswordFromRecoveryDirective])
+module.directive("tgInvitation", ["$tgAuth", "$tgConfirm", "$location", "$routeParams",
+                                  InvitationDirective])


Use collection get now that the backend supports it

--- app/collections/show-list.cjsx
+++ app/collections/show-list.cjsx
@@ -14,8 +14,7 @@
     page: 1 # start on page 1

   subjectsRequest: ->
-    # TODO Fix this on the backend should be able to do @state.collection.get('subjects')
-    apiClient.type('subjects').get(collection_id: @props.collection.id, page: @state.page)
+    @props.collection.get("subjects", page: @state.page)

   goToPage: (n) ->
     @transitionTo(@props.path, @props.params, {page: n})


Add test about router

Add test about router

--- test/router-test.coffee
+++ test/router-test.coffee
@@ -1,4 +1,12 @@
 require './spec_helper'
-require '../src/router'
+Ow = require '../src/index'
+
 describe "src/router", ->
-  it "should be written"
+  it "will mount target by pushContext", (done) ->
+    class TestContext extends Ow.Context
+      @component: class Test extends Ow.Component
+        componentWillMount: -> done()
+        render: -> React.createElement 'div', {}, 'test'
+
+    router = new Ow.Router Ow.DefaultLayout, null
+    router.pushContext(TestContext, {})


app now is global variable

app now is global variable

--- cli/tasks/build/server/bundle.coffee
+++ cli/tasks/build/server/bundle.coffee
@@ -107,6 +107,9 @@
             if err
                 return callback err

+            # make app object global in the file scope
+            file = "var app = #{file};\n"
+
             config =
                 platform: platform
                 release: options.release


Rename resizing properties

Rename resizing properties

--- src/trix/attachment_controller.coffee
+++ src/trix/attachment_controller.coffee
@@ -26,11 +26,11 @@
     @resizing =
       editor: editor = handleElement.parentElement
       image: editor.firstChild
-      width: parseInt(getDimensions(editor).width, 10)
-      startX: event.clientX
+      startWidth: parseInt(getDimensions(editor).width, 10)
+      startClientX: event.clientX

   didMoveMouseToResize: (event) =>
-    width = (@resizing.width + event.clientX - @resizing.startX) + "px"
+    width = (@resizing.startWidth + event.clientX - @resizing.startClientX) + "px"
     height = getDimensions(@resizing.image).height
     setStyle(@resizing.editor, {width, height})


Add WaterpointEditCtrl

Add WaterpointEditCtrl

--- app/scripts/controllers.coffee
+++ app/scripts/controllers.coffee
@@ -32,3 +32,18 @@
         wpt_code: 1
         status: 1
     , addMarkers
+  .controller 'WaterpointEditCtrl', ($scope, $http, $routeParams, Waterpoint, Form) ->
+    Waterpoint.get id: $routeParams.id, (waterpoint) ->
+      $scope.waterpoint = waterpoint
+    $scope.formTemplate = Form
+    $scope.save = () ->
+      etag = $scope.waterpoint._etag
+      # We need to remove these special attributes since they are not defined
+      # in the schema and the data will not validate and the update be rejected
+      for attr in ['_created', '_etag', '_id', '_links', '_updated']
+        $scope.waterpoint[attr] = undefined
+      $http.put('/api/waterpoints/'+$routeParams.id,
+                $scope.waterpoint,
+                headers: {'If-Match': etag})
+        .success (data, status, headers, config) ->
+          console.log data, status, headers, config


specs passing

specs passing

--- spec/Generator.spec.coffee
+++ spec/Generator.spec.coffee
@@ -330,10 +330,10 @@

           ctrlPath = path.join(@testHelper.testAppPath, "app", "controllers", "ngCars.js")
           expect(fs.existsSync ctrlPath).toBe true
-          expect(fs.readFileSync(ctrlPath).toString()).toMatch(/angular\.module\('ngCars/)
+          expect(fs.readFileSync(ctrlPath).toString()).toMatch(/ngCarsApp\.controller/)

           expect(fs.existsSync path.join(@testHelper.testAppPath, "app", "views", "ngCars", "index.html")).toBe true
-          expect(fs.existsSync path.join(@testHelper.testAppPath, "app", "views", "ngCars", "_details.html")).toBe true
+          expect(fs.existsSync path.join(@testHelper.testAppPath, "app", "views", "ngCars", "show.html")).toBe true

       it "fails when trying to overwrite existing files", ->


Update _tracklist.coffee

Update _tracklist.coffee

--- coffee/_tracklist.coffee
+++ coffee/_tracklist.coffee
@@ -58,21 +58,6 @@
           ).send()
       )

-      menu.append new gui.MenuItem(
-        label: 'Open in Youtube',
-        click: ->
-          artist = _this.find('.artist').text()
-          title = _this.find('.title').text()
-          request
-            url: 'http://gdata.youtube.com/feeds/api/videos?alt=json&max-results=1&q=' + encodeURIComponent(artist + ' - ' + title)
-            json: true
-            , (error, response, data) ->
-              if not data.feed.entry # no results
-                console.log('No results')
-              else
-                link = data.feed.entry[0].link[0].href
-                gui.Shell.openExternal(link)
-      )

     if $('#SideBar li.active').hasClass('playlist')
       menu.append new gui.MenuItem(type: 'separator')
@@ -92,6 +77,25 @@
             playlist_name
           ).send()
       )
+
+    menu.append new gui.MenuItem(
+      label: l10n.get('open_in_youtube'),
+      click: ->
+        artist = _this.find('.artist').text()
+        title = _this.find('.title').text()
+        request
+          # coffeelint: disable=max_line_length
+          url: 'http://gdata.youtube.com/feeds/api/videos?alt=json&max-results=1&q=' +
+          encodeURIComponent(artist + ' - ' + title)
+          # coffeelint: enable=max_line_length
+          json: true
+          , (error, response, data) ->
+            if not data.feed.entry # no results
+              console.log('No results')
+            else
+              link = data.feed.entry[0].link[0].href
+              gui.Shell.openExternal(link)
+    )

     menu.popup e.clientX, e.clientY
     false


latest sockets

latest sockets

--- sockets.coffee
+++ sockets.coffee
@@ -15,8 +15,6 @@
   listenSockets: =>
     @io.sockets.on 'connection', @addClient
   listenGPIO: =>
-    ready = =>
-      @gpio17.set 1

     @gpio17 = gpio.export 17,
       direction: 'out'
@@ -28,8 +26,15 @@
       ready: @gpio4Ready

     setTimeout =>
+      console.log 'setting 0'
+      @gpio17.set 0
+    , 1000
+
+    setTimeout =>
+      console.log 'setting 1'
       @gpio17.set 1
     , 2000
+

   gpio4Ready: =>
     @gpio4.on 'change', (val) =>


fix add members modal usage of membership model

--- src/node_modules/group-members/add-members-modal-controller.coffee
+++ src/node_modules/group-members/add-members-modal-controller.coffee
@@ -18,7 +18,7 @@
       return

     unsavedMembership = new MembershipModel({
-      user: $scope.model.selectedUser
+      member: $scope.model.selectedUser
       groupId: $scope.group.id
     })


Fix most treeview specs

Fix most treeview specs

--- spec/tree-view-spec.coffee
+++ spec/tree-view-spec.coffee
@@ -631,7 +631,7 @@
       describe "when a file is selected", ->
         it "opens an add dialog with the file's current directory path populated", ->
           expect(addDialog).toExist()
-          expect(addDialog.prompt.text()).toBeTruthy()
+          expect(addDialog.promptText.text()).toBeTruthy()
           expect(project.relativize(dirPath)).toMatch(/[^\/]$/)
           expect(addDialog.miniEditor.getText()).toBe(project.relativize(dirPath) + "/")
           expect(addDialog.miniEditor.getCursorBufferPosition().column).toBe addDialog.miniEditor.getText().length
@@ -670,8 +670,8 @@
               addDialog.miniEditor.insertText(path.basename(newPath))
               addDialog.trigger 'core:confirm'

-              expect(addDialog.prompt.text()).toContain 'Error'
-              expect(addDialog.prompt.text()).toContain 'already exists'
+              expect(addDialog.promptText.text()).toContain 'Error'
+              expect(addDialog.promptText.text()).toContain 'already exists'
               expect(addDialog).toHaveClass('error')
               expect(addDialog.hasParent()).toBeTruthy()

@@ -710,8 +710,8 @@
               addDialog.miniEditor.insertText("new-dir/")
               addDialog.trigger 'core:confirm'

-              expect(addDialog.prompt.text()).toContain 'Error'
-              expect(addDialog.prompt.text()).toContain 'already exists'
+              expect(addDialog.promptText.text()).toContain 'Error'
+              expect(addDialog.promptText.text()).toContain 'already exists'
               expect(addDialog).toHaveClass('error')
               expect(addDialog.hasParent()).toBeTruthy()

@@ -737,7 +737,7 @@
           addDialog = rootView.find(".tree-view-dialog").view()

           expect(addDialog).toExist()
-          expect(addDialog.prompt.text()).toBeTruthy()
+          expect(addDialog.promptText.text()).toBeTruthy()
           expect(project.relativize(dirPath)).toMatch(/[^\/]$/)
           expect(addDialog.miniEditor.getText()).toBe(project.relativize(dirPath) + "/")
           expect(addDialog.miniEditor.getCursorBufferPosition().column).toBe addDialog.miniEditor.getText().length
@@ -779,7 +779,7 @@
           extension = path.extname(filePath)
           fileNameWithoutExtension = path.basename(filePath, extension)
           expect(moveDialog).toExist()
-          expect(moveDialog.prompt.text()).toBe "Enter the new path for the file."
+          expect(moveDialog.promptText.text()).toBe "Enter the new path for the file."
           expect(moveDialog.miniEditor.getText()).toBe(project.relativize(filePath))
           expect(moveDialog.miniEditor.getSelectedText()).toBe path.basename(fileNameWithoutExtension)
           expect(moveDialog.miniEditor.isFocused).toBeTruthy()
@@ -827,8 +827,8 @@

                 moveDialog.trigger 'core:confirm'

-                expect(moveDialog.prompt.text()).toContain 'Error'
-                expect(moveDialog.prompt.text()).toContain 'already exists'
+                expect(moveDialog.promptText.text()).toContain 'Error'
+                expect(moveDialog.promptText.text()).toContain 'already exists'
                 expect(moveDialog).toHaveClass('error')
                 expect(moveDialog.hasParent()).toBeTruthy()


fix non-TextEditor path

fix non-TextEditor path

--- lib/agda-mode.coffee
+++ lib/agda-mode.coffee
@@ -67,17 +67,17 @@
             editorElement.classList.remove 'agda'
             subscription.dispose()

-    # editor active/inactive event
+    # editor active/inactive event
     registerEditorActivation: ->
         previousEditor = atom.workspace.getActivePaneItem()
         atom.workspace.onDidChangeActivePaneItem (nextEditor) =>
             if nextEditor
-                log 'Editor', "#{previousEditor.getPath()} == switch to => #{nextEditor.getPath()}"
+                log 'Editor', "#{previousEditor.getPath?()} == switch to => #{nextEditor.getPath?()}"
                 previousEditor.core?.deactivate()
                 nextEditor.core?.activate()
                 previousEditor = nextEditor
             else
-                log 'Editor', "#{previousEditor.getPath()} == switch to => NONE"
+                log 'Editor', "#{previousEditor.getPath?()} == switch to => NONE"
                 previousEditor.core?.deactivate()

     commands: [


Ensuring constructors invoked with splats behave correctly, along with caching.

--- nodes.coffee
+++ nodes.coffee
@@ -479,11 +479,14 @@
       for arg in @args
         arg.contains (n) -> mentionsArgs or= n instanceof LiteralNode and (n.value is 'arguments')
       utility 'extends'
+      a = o.scope.freeVariable()
+      b = o.scope.freeVariable()
+      c = o.scope.freeVariable()
       """
       #{@first}(function() {
       #{@idt(1)}var ctor = function(){};
-      #{@idt(1)}__extends(ctor, #{meth});
-      #{@idt(1)}return #{meth}.apply(new ctor, #{ @compileSplatArguments(o) });
+      #{@idt(1)}__extends(ctor, #{a} = #{meth});
+      #{@idt(1)}return typeof (#{b} = #{meth}.apply(#{c} = new ctor, #{ @compileSplatArguments(o) })) === "object" ? #{b} : #{c};
       #{@tab}}).#{ if mentionsArgs then 'apply(this, arguments)' else 'call(this)'}#{@last}
       """
     else


getting rid of excessive spaces after `Class.name = "Class"` assignment

--- nodes.coffee
+++ nodes.coffee
@@ -942,7 +942,8 @@
     @ensureConstructor name
     @body.spaced = yes
     @body.expressions.unshift @ctor unless @ctor instanceof Code
-    @body.expressions.unshift new Literal "#{name}.name = '#{name}'" if decl
+    if decl
+      @body.expressions.unshift new Assign (new Value (new Literal name), [new Access new Literal 'name']), (new Literal "'#{name}'")
     @body.expressions.push lname
     @addBoundFunctions o


nodes: removed `literal` helper

nodes: removed `literal` helper

--- nodes.coffee
+++ nodes.coffee
@@ -63,7 +63,7 @@
     pair = unless @isComplex()
       [this, this]
     else
-      reference = literal o.scope.freeVariable 'ref'
+      reference = new Literal o.scope.freeVariable 'ref'
       compiled  = new Assign reference, this
       [compiled, reference]
     (pair[i] = node.compile o) for node, i in pair if options?.precompile
@@ -352,11 +352,11 @@
       return [this, this]  # `a` `a.b`
     base = new Value @base, @properties.slice 0, -1
     if base.isComplex()  # `a().b`
-      bref = literal o.scope.freeVariable 'base'
+      bref = new Literal o.scope.freeVariable 'base'
       base = new Value new Parenthetical new Assign bref, base
     return [base, bref] unless name  # `a()`
     if name.isComplex()  # `a[b()]`
-      nref = literal o.scope.freeVariable 'name'
+      nref = new Literal o.scope.freeVariable 'name'
       name = new Index new Assign nref, name.index
       nref = new Index nref
     [base.push(name), new Value(bref or base.base, [nref or name])]
@@ -390,7 +390,7 @@
       fst = new Value @base, @properties.slice 0, i
       snd = new Value @base, @properties.slice i
       if fst.isComplex()
-        ref = literal o.scope.freeVariable 'ref'
+        ref = new Literal o.scope.freeVariable 'ref'
         fst = new Parenthetical new Assign ref, fst
         snd.base = ref
       ifn = new If new Existence(fst), snd, operation: yes
@@ -493,7 +493,7 @@
         [left, rite] = val.cacheReference o
         rite = new Call rite, @args
       else
-        left = literal @superReference o
+        left = new Literal @superReference o
         rite = new Call new Value(left), @args
         rite.isNew = @isNew
       left = "typeof #{ left.compile o } !== \"function\""
@@ -558,7 +558,7 @@

   # Hooks one constructor into another's prototype chain.
   compileNode: (o) ->
-    ref =  new Value literal utility 'extends'
+    ref =  new Value new Literal utility 'extends'
     (new Call ref, [@child, @parent]).compile o

 #### Accessor
@@ -765,7 +765,7 @@
   # list of prototype property assignments.
   constructor: (variable, @parent, @properties) ->
     super()
-    @variable = if variable is '__temp__' then literal variable else variable
+    @variable = if variable is '__temp__' then new Literal variable else variable
     @properties or= []
     @returns    = false

@@ -778,7 +778,7 @@
   # constructor, property assignments, and inheritance getting built out below.
   compileNode: (o) ->
     {variable} = this
-    variable   = literal o.scope.freeVariable 'ctor' if variable.value is '__temp__'
+    variable   = new Literal o.scope.freeVariable 'ctor' if variable.value is '__temp__'
     extension  = @parent and new Extends variable, @parent
     props      = new Expressions
     o.top      = true
@@ -787,9 +787,9 @@
     constScope = null

     if @parent
-      applied = new Value(@parent, [new Accessor(literal('apply'))])
+      applied = new Value @parent, [new Accessor new Literal 'apply']
       constructor = new Code([], new Expressions([
-        new Call(applied, [literal('this'), literal('arguments')])
+        new Call applied, [new Literal('this'), new Literal('arguments')]
       ]))
     else
       constructor = new Code
@@ -800,11 +800,11 @@
         if func not instanceof Code
           [func, ref] = func.compileReference o
           props.push func if func isnt ref
-          apply = new Call(new Value(ref, [new Accessor literal 'apply']), [literal('this'), literal('arguments')])
+          apply = new Call(new Value(ref, [new Accessor new Literal 'apply']), [new Literal('this'), new Literal('arguments')])
           func  = new Code [], new Expressions([apply])
         throw new Error "cannot define a constructor as a bound function." if func.bound
         func.name = className
-        func.body.push new Return literal 'this'
+        func.body.push new Return new Literal 'this'
         variable = new Value variable
         variable.namespaced = include func.name, '.'
         constructor = func
@@ -817,8 +817,8 @@
           constScope or= new Scope(o.scope, constructor.body, constructor)
           me or= constScope.freeVariable 'this'
           pname = pvar.compile(o)
-          constructor.body.push    new Return literal 'this' if constructor.body.empty()
-          constructor.body.unshift literal "this.#{pname} = function(){ return #{className}.prototype.#{pname}.apply(#{me}, arguments); }"
+          constructor.body.push    new Return new Literal 'this' if constructor.body.empty()
+          constructor.body.unshift new Literal "this.#{pname} = function(){ return #{className}.prototype.#{pname}.apply(#{me}, arguments); }"
       if pvar
         access = if prop.context is 'this' then pvar.base.properties[0] else new Accessor(pvar, 'prototype')
         val    = new Value variable, [access]
@@ -826,7 +826,7 @@
       props.push prop

     constructor.className = className.match /[\w\d\$_]+$/
-    constructor.body.unshift literal "#{me} = this" if me
+    constructor.body.unshift new Literal "#{me} = this" if me
     construct = @idt() + new Assign(variable, constructor).compile(merge o, sharedScope: constScope) + ';'
     props     = if !props.empty() then '\n' + props.compile(o)                    else ''
     extension = if extension      then '\n' + @idt() + extension.compile(o) + ';' else ''
@@ -890,7 +890,7 @@
       else
         idx = if isObject
           if obj.tags.this then obj.properties[0].name else obj
-        else literal 0
+        else new Literal 0
       value = new Value value unless value instanceof Value
       accessClass = if IDENTIFIER.test idx.value then Accessor else Index
       value.properties.push new accessClass idx
@@ -914,11 +914,11 @@
         throw new Error 'pattern matching must use only identifiers on the left-hand side.'
       accessClass = if isObject and IDENTIFIER.test(idx.value) then Accessor else Index
       if not splat and obj instanceof Splat
-        val   = literal obj.compileValue o, valVar, i, olength - i - 1
+        val   = new Literal obj.compileValue o, valVar, i, olength - i - 1
         splat = true
       else
-        idx = literal(if splat then "#{valVar}.length - #{olength - idx}" else idx) if typeof idx isnt 'object'
-        val = new Value literal(valVar), [new accessClass idx]
+        idx = new Literal(if splat then "#{valVar}.length - #{olength - idx}" else idx) if typeof idx isnt 'object'
+        val = new Value new Literal(valVar), [new accessClass idx]
       assigns.push new Assign(obj, val).compile otop
     assigns.push valVar unless top
     code = assigns.join ', '
@@ -971,14 +971,14 @@
     for param, i in @params
       if splat
         if param.attach
-          param.assign = new Assign new Value literal('this'), [new Accessor param.value]
+          param.assign = new Assign new Value new Literal('this'), [new Accessor param.value]
           @body.expressions.splice splat.index + 1, 0, param.assign
         splat.trailings.push param
       else
         if param.attach
           {value} = param
-          [param, param.splat] = [literal(o.scope.freeVariable 'arg'), param.splat]
-          @body.unshift new Assign new Value(literal('this'), [new Accessor value]), param
+          [param, param.splat] = [new Literal(o.scope.freeVariable 'arg'), param.splat]
+          @body.unshift new Assign new Value(new Literal('this'), [new Accessor value]), param
         if param.splat
           splat           = new Splat param.value
           splat.index     = i
@@ -1017,7 +1017,7 @@

   constructor: (@name, @attach, @splat) ->
     super()
-    @value = literal @name
+    @value = new Literal @name

   compileNode: (o) ->
     @value.compile o
@@ -1026,7 +1026,7 @@
     {name} = @
     name = '@' + name if @attach
     name += '...'     if @splat
-    literal(name).toString()
+    new Literal(name).toString()

 #### Splat

@@ -1038,7 +1038,7 @@

   constructor: (name) ->
     super()
-    name = literal(name) unless name.compile
+    name = new Literal name unless name.compile
     @name = name

   compileNode: (o) ->
@@ -1059,7 +1059,7 @@
       for trailing, idx in @trailings
         if trailing.attach
           assign        = trailing.assign
-          trailing      = literal o.scope.freeVariable 'arg'
+          trailing      = new Literal o.scope.freeVariable 'arg'
           assign.value  = trailing
         pos = @trailings.length - idx
         o.scope.assign(trailing.compile(o), "arguments[#{variadic} ? #{len} - #{pos} : #{@index + idx}]")
@@ -1135,7 +1135,7 @@
     pre     = "#{set}#{@tab}while (#{cond})"
     @body   = Expressions.wrap([new If(@guard, @body)]) if @guard
     if @returns
-      post = '\n' + new Return(literal(rvar)).compile(merge(o, indent: @idt()))
+      post = '\n' + new Return(new Literal rvar).compile(merge(o, indent: @idt()))
     else
       post = ''
     "#{pre} {\n#{ @body.compile(o) }\n#{@tab}}#{post}"
@@ -1233,7 +1233,7 @@
   compileExistence: (o) ->
     if @first.isComplex()
       ref = o.scope.freeVariable 'ref'
-      fst = new Parenthetical new Assign literal(ref), @first
+      fst = new Parenthetical new Assign new Literal(ref), @first
     else
       fst = @first
       ref = fst.compile o
@@ -1405,7 +1405,7 @@
     this

   compileReturnValue: (val, o) ->
-    return '\n' + new Return(literal(val)).compile(o) if @returns
+    return '\n' + new Return(new Literal val).compile(o) if @returns
     return '\n' + val if val
     ''

@@ -1441,7 +1441,7 @@
         sourcePart = "(#{sourcePart})" unless @object
         svar = ref
       namePart = if @pattern
-        new Assign(@name, literal "#{svar}[#{ivar}]").compile merge o, top: on
+        new Assign(@name, new Literal "#{svar}[#{ivar}]").compile merge o, top: on
       else if name
         "#{name} = #{svar}[#{ivar}]"
       unless @object
@@ -1454,9 +1454,9 @@
     if @guard
       body        = Expressions.wrap([new If(@guard, body)])
     if codeInBody
-      body.unshift  literal "var #{name} = #{ivar}" if range
-      body.unshift  literal "var #{namePart}" if namePart
-      body.unshift  literal "var #{index} = #{ivar}" if index
+      body.unshift  new Literal "var #{name} = #{ivar}" if range
+      body.unshift  new Literal "var #{namePart}" if namePart
+      body.unshift  new Literal "var #{index} = #{ivar}" if index
       body        = Closure.wrap(body, true)
     else
       varPart     = "#{idt1}#{namePart};\n" if namePart
@@ -1483,7 +1483,7 @@
   constructor: (@subject, @cases, @otherwise) ->
     super()
     @tags.subjectless = !@subject
-    @subject or= literal 'true'
+    @subject or= new Literal 'true'

   makeReturn: ->
     pair[1].makeReturn() for pair in @cases
@@ -1607,7 +1607,7 @@
   wrap: (name, expressions) ->
     return expressions if expressions.empty() or expressions.containsPureStatement()
     Expressions.wrap [new Call(
-      new Value literal(name), [new Accessor literal 'push']
+      new Value new Literal(name), [new Accessor new Literal 'push']
       [expressions.unwrap()]
     )]

@@ -1625,9 +1625,9 @@
     args = []
     if (mentionsArgs = expressions.contains @literalArgs) or
        (               expressions.contains @literalThis)
-      meth = literal if mentionsArgs then 'apply' else 'call'
-      args = [literal 'this']
-      args.push literal 'arguments' if mentionsArgs
+      meth = new Literal if mentionsArgs then 'apply' else 'call'
+      args = [new Literal 'this']
+      args.push new Literal 'arguments' if mentionsArgs
       func = new Value func, [new Accessor meth]
     call = new Call func, args
     if statement then Expressions.wrap [call] else call
@@ -1683,9 +1683,6 @@
 # Utility Functions
 # -----------------

-# Handy helper for generating a Literal.
-literal = (name) -> new Literal name
-
 # Helper for ensuring that utility functions are assigned at the top level.
 utility = (name) ->
   ref = "__#{name}"


abb11c80d1 didn't consider objects with [[Call]]

--- nodes.coffee
+++ nodes.coffee
@@ -586,9 +586,9 @@
       return """
         (function(func, args, ctor) {
         #{idt}ctor.prototype = func.prototype;
-        #{idt}var child = new ctor, result = func.apply(child, args);
-        #{idt}return typeof result === "object" && result !== null ? result : child;
-        #{@tab}})(#{ @variable.compile o, LEVEL_LIST }, #{splatArgs}, function() {})
+        #{idt}var child = new ctor, result = func.apply(child, args), t = typeof result;
+        #{idt}return t == "object" || t == "function" ? result || child : child;
+        #{@tab}})(#{ @variable.compile o, LEVEL_LIST }, #{splatArgs}, function(){})
       """
     base = new Value @variable
     if (name = base.properties.pop()) and base.isComplex()


[client] fix what i just broke with file_picker

--- client/app/components/message.coffee
+++ client/app/components/message.coffee
@@ -1,6 +1,6 @@
 {div, ul, li, span, i, p, h3, a, button, pre, iframe} = React.DOM
 Compose      = require './compose'
-FilePicker   = require './file-picker'
+FilePicker   = require './file_picker'
 MessageUtils = require '../utils/message_utils'
 {ComposeActions, MessageFlags} = require '../constants/app_constants'
 LayoutActionCreator       = require '../actions/layout_action_creator'
@@ -152,7 +152,10 @@
                     span className: 'hour', prepared.date
                 if hasAttachments
                     div className: 'col-md-4',
-                        FilePicker({editable: false, files: prepared.attachments.map(MessageUtils.convertAttachments), display: display})
+                        FilePicker
+                            editable: false
+                            value: files: prepared.attachments.map(MessageUtils.convertAttachments)
+                            display: display
             div className: 'full-headers',
                 pre null, prepared.fullHeaders.join "\n"
             if @state.messageDisplayHTML and prepared.html


app/mainctrl: register ShortcutsController as shortcuts

--- client/app/lib/maincontroller.coffee
+++ client/app/lib/maincontroller.coffee
@@ -139,9 +139,9 @@
     kd.registerSingleton 'search',                    new SearchController
     kd.registerSingleton 'realtime',                  new RealtimeController
     kd.registerSingleton 'pageTitle',                 new PageTitleController
-    kd.registerSingleton 'shortcutsController',  kb = new ShortcutsController
-
-    kb.addEventListeners()
+    kd.registerSingleton 'shortcuts',     shortcuts = new ShortcutsController
+
+    shortcuts.addEventListeners()

     router.listen()


References #81, configure JQuery methods to toggle bug fields

--- app/assets/javascripts/biodiversity_reports.js.coffee
+++ app/assets/javascripts/biodiversity_reports.js.coffee
@@ -33,3 +33,11 @@
     $(@).find(':input:not([type=hidden])').val('')
     $(@).find('#biodiversity_report_lichen_sample_attributes__destroy').val('1')
     $('#lnk-toggle-lichen-fields').text('Add lichen sample')
+
+  $('#macroinvertebrate_fields').on 'shown.bs.collapse', (event) ->
+    $('#lnk-toggle-macroinvertebrate-fields').text('Omit macroinvertebrate sample')
+
+  $('#macroinvertebrate_fields').on 'hidden.bs.collapse', (event) ->
+    $(@).find(':input:not([type=hidden])').val('')
+    $(@).find('#biodiversity_report_macroinvertebrate_sample_attributes__destroy').val('1')
+    $('#lnk-toggle-macroinvertebrate-fields').text('Add macroinvertebrate sample')


clean coffee and add a bitter variation, might not need it

--- app/templates/_Gulpfile.coffee
+++ app/templates/_Gulpfile.coffee
@@ -93,21 +93,20 @@
 #*------------------------------------*/
 gulp.task "coffee", () ->
 	gulp.src([conf.path.coffee+"/**/*.coffee"])
-		.pipe(plumber())
-		.pipe(coffee({bare: true})).on('error', gutil.log)
-		.pipe(concat("built#{ext}.js"))
-		.pipe( if isProd then uglify() else gutil.noop() )
-		.pipe(gulp.dest(conf.path.js))
+		.pipe plumber()
+		.pipe coffee({bare: true})).on('error', gutil.log
+		.pipe uglify()
+		.pipe gulp.dest(conf.path.js)
 		.pipe reload({stream: true})
-# 	coffee:
-# 		dist:
-# 			files: [{
-# 				expand: true
-# 				cwd: '<%= conf.path.coffee %>'
-# 				src: '{,*/}*.coffee'
-# 				dest: '<%= conf.path.js %>'
-# 				ext: '.js'
-# 			}]
+
+# Until I figure a better way
+gulp.task "bitter-coffee", () ->
+	gulp.src([conf.path.coffee+"/**/*.coffee"])
+		.pipe plumber()
+		.pipe coffee({bare: true})).on('error', gutil.log
+		.pipe uglify()
+		.pipe gulp.dest(conf.path.js)
+		.pipe reload({stream: true})

 #*------------------------------------*\


Fixes dashed modules producing invalid test files.

--- app/templates/test/spec.coffee
+++ app/templates/test/spec.coffee
@@ -3,12 +3,12 @@
 chai = require 'chai'
 sinon = require 'sinon'
 # using compiled JavaScript file here to be sure module works
-<%= moduleName %> = require '../lib/<%= moduleName %>.js'
+<%= _.camelize(moduleName) %> = require '../lib/<%= moduleName %>.js'

 expect = chai.expect
 chai.use require 'sinon-chai'

 describe '<%= moduleName %>', ->
   it 'works', ->
-    actual = <%= moduleName %> 'World'
+    actual = <%= _.camelize(moduleName) %> 'World'
     expect(actual).to.eql 'Hello World'