Reduce the frequency of regenerating the navigation doc

--- scripts/gh-book/opf-file.coffee
+++ scripts/gh-book/opf-file.coffee
@@ -29,7 +29,11 @@
       # Contains root ToC nodes (like "Unit 1")
       @children = new Backbone.Collection()

-      setNavModel = () => @navModel.set 'body', @_serializeNavModel()
+      # Use the `parse:true` option instead of `loading:true` because
+      # Backbone sets this option when a model is being parsed.
+      # This way we can ignore firing events when Backbone is parsing as well as
+      # when we are internally updating models.
+      setNavModel = () => @navModel.set 'body', @_serializeNavModel(), {parse:true}

       # if the added node already exists in the tree then remove it.
       # @tocNodes.on 'add', (model, collection, options) =>
@@ -40,14 +44,14 @@
       #         parentChildren.remove model

-      @tocNodes.on 'tree:add',    (model, collection, options) => @tocNodes.add model
-      @tocNodes.on 'tree:remove', (model, collection, options) => @tocNodes.remove model
+      @tocNodes.on 'tree:add',    (model, collection, options) => @tocNodes.add model, options
+      @tocNodes.on 'tree:remove', (model, collection, options) => @tocNodes.remove model, options

       @tocNodes.on 'add remove tree:change', (model, collection, options) =>
-        setNavModel() if not options.loading
+        setNavModel() if not options.parse
       @tocNodes.on 'change reset', (collection, options) =>
         # HACK: `?` is because `inherits/container.add` calls `trigger('change')`
-        setNavModel() if not options?.loading
+        setNavModel() if not options?.parse

       @children.on 'add', (child, collection, options) =>
@@ -77,6 +81,8 @@
         .fail((err) => throw err)
         .done () =>
           @parseNavModel()
+          @listenTo @navModel, 'change:body', (model, value, options) =>
+            @parseNavModel() if not options.parse

     parseNavModel: () ->
@@ -104,26 +110,26 @@
             path = Utils.resolvePath(contextPath, href)
             model = allContent.get path

-            model.set 'title', title, {loading:true}
-            collection.add model
+            model.set 'title', title, {parse:true}
+            collection.add model, {parse:true}

             @listenTo model, 'change:title', () =>
               console.warn 'TODO: BUG: Change the title in the ToC'

           else if $span[0]
             model = new TocNode {title: $span.text(), attributes: attributes}
-            collection.add model
+            collection.add model, {parse:true}

             # Recurse
             recBuildTree(model.getChildren(), $ol, contextPath) if $ol[0]
           else throw 'ERROR: Invalid Navigation Tree Structure'

           # Add the model to the tocNodes so we can listen to changes and update the ToC HTML
-          @tocNodes.add model, {loading:true}
+          @tocNodes.add model, {parse:true}

       $root = $body.find('nav > ol')
-      @tocNodes.reset [@], {loading:true}
+      @tocNodes.reset [@], {parse:true}
       @children.reset()
       recBuildTree(@children, $root, @navModel.id)


Don't show comment box when not signed in

--- core/app/backbone/views/evidence/evidence_container_view.coffee
+++ core/app/backbone/views/evidence/evidence_container_view.coffee
@@ -25,9 +25,13 @@
     formRegion: '.js-form-region'

   onRender: ->
-    @_addEvidenceFormView = new AddEvidenceFormView collection: @collection
-    @formRegion.show @_addEvidenceFormView
-    @opinionHelpRegion.show new OpinionHelpView collection: @collection
+    if Factlink.Global.signed_in
+      @_addEvidenceFormView = new AddEvidenceFormView collection: @collection
+      @formRegion.show @_addEvidenceFormView
+
+    if !Factlink.Global.signed_in
+      @opinionHelpRegion.show new OpinionHelpView collection: @collection
+
     @collectionRegion.show new EvidenceCollectionView collection: @collection
     @_updateLoading()


Add selected syntax tag to the list by default

--- client/landing/app/Applications/Activity.kdapplication/widgets/codesnippetwidget.coffee
+++ client/landing/app/Applications/Activity.kdapplication/widgets/codesnippetwidget.coffee
@@ -69,7 +69,7 @@
         updateWidget = @getDelegate()
         blacklist = (data.getId() for data in @tagController.getSelectedItemData() when 'function' is typeof data.getId)
         appManager.tell "Topics", "fetchTopics", {inputValue, blacklist}, callback
-
+
     @tagAutoComplete = @tagController.getView()

     @loader = new KDLoaderView
@@ -83,7 +83,7 @@
         range       : 0.4
         speed       : 1
         FPS         : 24
-      click         : =>
+      click         : =>
         log "ASDASDAS"

     @syntaxSelect = new KDSelectBox
@@ -92,7 +92,21 @@
       defaultValue  : "javascript"
       callback      : (value) => @emit "codeSnip.changeSyntax", value

+    @updateSyntaxTag = (syntax)=>
+      # Remove already appended syntax tag from submit queue if exists
+      oldSyntax = @ace.getSyntax()
+      subViews = @tagController.itemWrapper.getSubViews().slice()
+      for item in subViews
+        if item.getData().title is oldSyntax
+          @tagController.removeFromSubmitQueue(item)
+
+      {selectedItemsLimit} = @tagController.getOptions()
+      # Add new syntax tag to submit queue
+      if @tagController.selectedItemCounter < selectedItemsLimit
+        @tagController.addItemToSubmitQueue @tagController.getNoItemFoundView(syntax)
+
     @on "codeSnip.changeSyntax", (syntax)=>
+      @updateSyntaxTag syntax
       @ace.setSyntax syntax

   submit:=>
@@ -108,18 +122,19 @@
     @ace.setContents "//your code snippet goes here..."
     @syntaxSelect.setValue 'javascript'
     @tagController.reset()
+    @updateSyntaxTag 'javascript'

   switchToEditView:(activity)->
     @submitBtn.setTitle "Edit code snippet"
     @addCustomData "activity", activity
     {title, body, tags} = activity
     {syntax, content} = activity.attachments[0]
-
+
     @tagController.reset()
     @tagController.setDefaultValue tags or []
-
+
     fillForm = =>
-      @title.setValue Encoder.htmlDecode title
+      @title.setValue Encoder.htmlDecode title
       @description.setValue Encoder.htmlDecode body
       @ace.setContents Encoder.htmlDecode content
       @syntaxSelect.setValue Encoder.htmlDecode syntax
@@ -148,6 +163,7 @@
       @ace.setTheme()
       @ace.setSyntax "javascript"
       @ace.editor.getSession().on 'change', => @refreshEditorView()
+      @updateSyntaxTag 'javascript'
       @emit "codeSnip.aceLoaded"

   refreshEditorView:->


Explicitly declare ‘-h’ option

Explicitly declare ‘-h’ option

--- src/cli/argv.coffee
+++ src/cli/argv.coffee
@@ -7,6 +7,10 @@
 	opts = parser
 	.script 'brave-mouse'
 	.options
+		help:
+			abbr: 'h'
+			flag: true
+			help: 'Print this help message'
 		version:
 			abbr: 'v'
 			flag: true


Show user status for livechat rooms

Show user status for livechat rooms

--- packages/rocketchat-ui/views/app/room.coffee
+++ packages/rocketchat-ui/views/app/room.coffee
@@ -61,10 +61,9 @@

 		return {} unless roomData

-		if roomData.t is 'd'
+		if roomData.t in ['d', 'l']
 			username = _.without roomData.usernames, Meteor.user().username
 			return Session.get('user_' + username + '_status') || 'offline'
-
 		else
 			return 'offline'


Fixing typo in usedev trigger

Fixing typo in usedev trigger

--- scripts/triggers.coffee
+++ scripts/triggers.coffee
@@ -23,7 +23,7 @@
   '!pb'           : "Please avoid using pastebin.com as it is slow and forces others to look at ads.  Please use #{pasteUrl}.  Thanks!"
   '!rules'        : "You may review our room rules at http://goo.gl/Tl77U2"
   '!docs'         : "The !docs syntax is deprecated, please use `Liona show <user> docs for <topic>`"
-  '!usedev'       : "Only use the dev version you don't mind potentially needing to change your app on updates if the API or concepts change. Stability is not guaranteed."
+  '!usedev'       : "Only use the dev version if you don't mind potentially needing to change your app on updates if the API or concepts change. Stability is not guaranteed."

   # Helpers
   '!ugt'          : "It is always morning when someone comes into a channel. We call that Universal Greeting Time http://www.total-knowledge.com/~ilya/mips/ugt.html"


Proxy calls to create/commandAggregate so they get queued (commands need too get executed in order)

--- src/command_service.coffee
+++ src/command_service.coffee
@@ -1,3 +1,4 @@
+async = require 'async'
 eventric = require 'eventric'

 DomainEventService = eventric 'DomainEventService'
@@ -5,6 +6,26 @@
 class CommandService

   constructor: (@_domainEventService, @_aggregateRepository) ->
+    # proxy & queue public api
+    _queue = async.queue (payload, next) =>
+      payload.originalFunction.call @, payload.arguments..., next
+    , 1
+
+    _proxy = (_originalFunctionName, _originalFunction) -> ->
+      originalCallback = arguments[arguments.length - 1]
+      delete arguments[arguments.length - 1]
+      _queue.push
+        originalFunction: _originalFunction
+        arguments: arguments
+      , originalCallback
+
+    for originalFunctionName, originalFunction of @
+      if typeof originalFunction isnt 'function' or originalFunctionName is 'constructor' or (originalFunctionName.indexOf '_') is 0
+        # only check non-constructor functions for now
+        continue
+
+      @[originalFunctionName] = _proxy originalFunctionName, originalFunction
+

   createAggregate: ([aggregateName, params]..., callback) ->
     AggregateClass = @_aggregateRepository.getClass aggregateName
@@ -31,10 +52,6 @@

       @_generateSaveAndTriggerDomainEvent 'create', aggregate, callback

-
-  commandAggregateClosure: (aggregateName, aggregateId) ->
-    ([commandName, params]..., callback) =>
-      @commandAggregate aggregateName, aggregateId, commandName, params, callback

   commandAggregate: ([aggregateName, aggregateId, commandName, params]..., callback) ->
     # get the aggregate from the AggregateRepository
@@ -67,13 +84,13 @@

     # get the DomainEvents and hand them over to DomainEventService
     domainEvents = aggregate.getDomainEvents()
-    @_domainEventService.saveAndTrigger domainEvents, (err) ->
+    @_domainEventService.saveAndTrigger domainEvents, (err) =>
       return callback err, null if err

       aggregate.clearChanges()

       # return the aggregateId
-      callback null, aggregate.id
+      callback? null, aggregate.id

 module.exports = CommandService


'no progress on consolidating tag user ids yet'

--- server/bootstrap.coffee
+++ server/bootstrap.coffee
@@ -44,6 +44,7 @@
             }
           'red': {
             users: [1]
+            weight: 1
             }
           'blue': {
             users: [2]
@@ -71,17 +72,20 @@
         author_id: 2
         content: 'Me too!!'
         parent_id: 0
+        tags:{}
       },
       {
         instance_id: 0
         author_id: 1
         content: 'I\'m a child.'
         parent_id: 0
+        tags:{}
       }
       {
         instance_id: 0
         author_id: 0
         content: 'whattsup'
+        tags:{}
       }
     ]
     timestamp = (new Date()).getTime()
@@ -103,9 +107,17 @@
       if 'instance_id' of post
         post.instance_id = instance_ids[post.instance_id]
       if 'tags' of post
-        for tag in [1..post.tags.length]
-          tag_user_ids = []
-          for user in [1..tag.users.length]
-            tag_user_ids.push user_ids[post[tag][user]]
-          post[tag].users = tag_user_ids
-      post_ids.push Posts.insert(post)+        for tag in post.tags
+          tagger_ids = []
+          for user in post.tag.users
+            tagger_ids.push user_ids[user]
+          post.tag.users = tagger_ids
+      Posts.insert(post)
+
+#         for tag in [0..post.tags.length-1]
+#           tag_user_ids = []
+#           for user in [0..tag.users.length-1]
+#             tag_user_ids.push post[tag][user]
+#             #tag_user_ids.push user_ids[post[tag][user]]
+#           post[tag].users = tag_user_ids
+#       Posts.insert(post)


unary negation has high precedence, shouldn't need parens

--- src/compiler.coffee
+++ src/compiler.coffee
@@ -410,10 +410,10 @@
   lrem: { compile: (b) -> b.push2 new Expr "util.long_mod(rs,$1,$0)",b.pop2(),b.pop2() }
   frem: { compile: (b) -> b.push new Expr "($1%$0)",b.pop(),b.pop() }
   drem: { compile: (b) -> b.push2 new Expr "($1%$0)",b.pop2(),b.pop2() }
-  ineg: { compile: (b) -> b.push new Expr "(-$0)",b.pop() }  # doesn't handle int_min edge case
+  ineg: { compile: (b) -> b.push new Expr "-$0",b.pop() }  # doesn't handle int_min edge case
   lneg: { compile: (b) -> b.push2 new Expr "$0.negate()",b.pop2() }
-  fneg: { compile: (b) -> b.push new Expr "(-$0)",b.pop() }
-  dneg: { compile: (b) -> b.push2 new Expr "(-$0)",b.pop2() }
+  fneg: { compile: (b) -> b.push new Expr "-$0",b.pop() }
+  dneg: { compile: (b) -> b.push2 new Expr "-$0",b.pop2() }
   ishl: { compile: (b) -> b.push new Expr "($1<<($0&0x1F))", b.pop(), b.pop() }
   lshl: { compile: (b) -> b.push new Expr "$1.shiftLeft(gLong.fromInt($0&0x3F))", b.pop(), b.pop2() }
   ishr: { compile: (b) -> b.push new Expr "($1>>($0&0x1F))", b.pop(), b.pop() }


payment: indicate current plan, disable lower ones (conditionally)

we can pass through an option "forceUpgrade" which will disable the lower price tiers.  This, so that we can indicate where your current plan is relative to the price tier, but still force the user to upgrade to a larger plan

--- client/app/MainApp/payment/planupgradeform.coffee
+++ client/app/MainApp/payment/planupgradeform.coffee
@@ -2,6 +2,23 @@

   setPlans: (plans) ->
     @listController.instantiateListItems plans
+
+    return this
+
+  setCurrentSubscription: (subscription, forceUpgrade = no) ->
+    lowerTier = yes
+
+    for own code, view of @planViewsByCode
+
+      if code is subscription.planCode
+        view.activate?()
+        view.disable?()
+        lowerTier = no
+
+      else if forceUpgrade and lowerTier
+        view.disable?()
+
+    return this

   viewAppended: ->
     @listController = new KDListViewController
@@ -9,11 +26,16 @@

     @listView = @listController.getListView()

+    @planViewsByCode = {}
     @listView.on 'ItemWasAdded', (item) =>
+      plan = item.getData()
+
+      @planViewsByCode[plan.planCode] = item
+
       item.setControls new KDButtonView
         title     : 'Upgrade'
         callback  : =>
-          @emit 'PlanSelected', item.getData()
+          @emit 'PlanSelected', plan

     super()


Fallback for invalid times/dates

Fallback for invalid times/dates

--- src/directives/time.coffee
+++ src/directives/time.coffee
@@ -53,7 +53,14 @@

         $scope.reset = ->
           # TODO: Have this be looser with formatting (only accepts XX:XX XX)
-          $scope.time = new Date Date.parse "Jan 1, 1970, " + opts.default
+          prefix = "Jan 1, 1970, "
+          time   = new Date (prefix + opts.default)
+
+          if isNaN time.getTime()
+            # Invalid Date
+            time = new Date prefix + "12:00 AM"
+
+          $scope.time = time

         #
         # @name inputSelectAction


fix(macTime): Re-render time input view even when viewValue is the same

--- src/directives/time.coffee
+++ src/directives/time.coffee
@@ -116,9 +116,8 @@
       updateInput = ->
         displayTime = $filter("date") time.getTime(), "hh:mm a"

-        unless displayTime is ngModelCtrl.$viewValue
-          ngModelCtrl.$setViewValue displayTime
-          ngModelCtrl.$render()
+        ngModelCtrl.$setViewValue displayTime
+        ngModelCtrl.$render()

       updateTime = ->
         if timeMatch = util.timeRegex.exec ngModelCtrl.$modelValue


refactored sort method

refactored sort method

--- src/drm-tablesorter.coffee
+++ src/drm-tablesorter.coffee
@@ -2,170 +2,207 @@
 # Sort Tabular Data
 ###############################################################################
 "use strict"
-
+# TODO: merge with drm-sort to create a single sorting library that will sort any type of elements
+# major difference is the use of columnNum to find data to be sorted
+$ = jQuery
 class @DrmTableSorter
-    constructor: (@tableClass = 'drm-sortable-table') ->
+    constructor: (@list = $('.drm-sortable-table'), @buttonClass = 'drm-sortable-table-button', @activeClass = 'active', @ignoreWords = ['a', 'the']) ->
         self = @
-        self.table = $ ".#{@tableClass}"
-        self.buttonClass = 'drm-sortable-table-button'
-
-        self.table.on 'click', ".#{@buttonClass}", ->
-            that = $ @
-            columnNum = that.closest('th').index()
-            self.addActiveClass.call @
-            self.renderTable that.data('dir'), columnNum
-
-    addActiveClass: ->
-        that = $ @
-        row = that.closest 'tr'
-        row.find('.drm-sortable-table-button.active').removeClass 'active'
-        that.addClass 'active'
-
-    getData: (columnNum) =>
-        values = []
-        rows = @table.find 'tbody tr'
-
-        $.each rows, (key, value) ->
-            text = $.trim $(value).find('td').eq(columnNum).text()
-            if text.length > 0 then values.push text
-
-        values
-
-    sortRows: (direction, columnNum) =>
-        self = @
-        rows = self.table.find 'tbody tr'
-
-        _patterns =
+        # parent list element
+        tableBody = @list.find 'tbody'
+        @rows = tableBody.find 'tr'
+
+        @list.on 'click', ".#{@buttonClass}", ->
+            _that = $ @
+            columnNum = _that.closest('th').index()
+            self.toggleActiveClass.call @, self.activeClass, 'tr'
+            sortedRows = self.sortList _that.data('dir'), columnNum, self.rows
+            self.renderSort sortedRows, tableBody
+
+    toggleActiveClass: (className, parent) ->
+        $(@).closest(parent).find(".#{className}").removeClass(className).end().end().addClass className
+
+    sortList: (direction, columnNum, listItems) =>
+        patterns =
             number: new RegExp "^(?:\\-?\\d+|\\d*)(?:\\.?\\d+|\\d)"
             alpha: new RegExp '^[a-z ,.\\-]*','i'
             # mm/dd/yyyy
             monthDayYear: new RegExp '^(?:[0]?[1-9]|[1][012]|[1-9])[-\/.](?:[0]?[1-9]|[12][0-9]|[3][01])(?:[-\/.][0-9]{4})'
             # 00:00pm
             time: new RegExp '^(?:[12][012]|[0]?[0-9]):[012345][0-9](?:am|pm)', 'i'
-
-        _getDataType = (columnNum) ->
-            types = []
-            values = self.getData columnNum
-
-            _isDate = (value) ->
-                if _patterns.monthDayYear.test(value) then true else false
-
-            _isNumber = (value) ->
-                if _patterns.number.test(value) then true else false
-
-            _isAlpha = (value) ->
-                if _patterns.alpha.test(value) then true else false
-
-            _isTime = (value) ->
-                if _patterns.time.test(value) then true else false
-
-            $.each values, (key, value) ->
-                if _isDate.call self, value
-                    types.push 'date'
-                else if _isTime.call self, value
-                    types.push 'time'
-                else if _isNumber.call self, value
-                    types.push 'number'
-                else if _isAlpha.call self, value
-                    types.push 'alpha'
+            hour: new RegExp '^(\\d+)'
+            minute: new RegExp ':(\\d+)'
+            ampm: new RegExp '(am|pm|AM|PM)$'
+
+        sortUtilities =
+            capitalize: (str) ->
+                str.toLowerCase().replace /^.|\s\S/g, (a) ->
+                    a.toUpperCase()
+
+            getValues: (listItems, columnNum) ->
+                # creates an array of values from list items
+                values = []
+
+                listItems.each ->
+                    value = $(@).find('td').eq(columnNum).text()
+                    values.push $.trim(value)
+
+                return values
+
+            parseTime: (time) ->
+                _hour = parseInt(patterns.hour.exec(time)[1], 10)
+                _minutes = patterns.minute.exec(time)[1]
+                _ampm = patterns.ampm.exec(time)[1].toLowerCase()
+
+                if _ampm is 'am'
+                    _hour = _hour.toString()
+
+                    if _hour is '12'
+                        _hour = '0'
+                    else if _hour.length is 1
+                        _hour = "0#{_hour}"
+
+                    return "#{_hour}:#{_minutes}"
+
+                else if _ampm is 'pm'
+                    return "#{_hour + 12}:#{_minutes}"
+
+            cleanAlpha: (str, ignoreWords = []) ->
+                # removes leading 'the' or 'a'
+                $.each ignoreWords, ->
+                    re = new RegExp "^#{@}\\s", 'i'
+                    str = str.replace re, ''
+                    return str
+
+                return str
+
+            sortValues: (a, b, direction = 'ascending') ->
+                # test for alpha values and perform alpha sort
+                if patterns.alpha.test(a)
+                    if a < b
+                        return if direction is 'ascending' then -1 else 1
+                    else if a > b
+                        return if direction is 'ascending' then 1 else -1
+                    else if a is b
+                        return 0
+                # if values are not alpha perform an numeric sort
                 else
-                    types.push null
-
-            if $.inArray('alpha', types) isnt -1 then 'alpha' else types[0]
-
-        type = _getDataType columnNum
-        if !type
-            null
-
-        else if type is 'date'
-            _sortAsc = (a, b) ->
-                a = new Date _patterns.monthDayYear.exec($.trim($(a).find('td').eq(columnNum).text()))
-                b = new Date _patterns.monthDayYear.exec($.trim($(b).find('td').eq(columnNum).text()))
-                a - b
-
-            _sortDesc = (a, b) ->
-                a = new Date _patterns.monthDayYear.exec($.trim($(a).find('td').eq(columnNum).text()))
-                b = new Date _patterns.monthDayYear.exec($.trim($(b).find('td').eq(columnNum).text()))
-                b - a
-
-            if direction is 'ascending' then rows.sort _sortAsc else rows.sort _sortDesc
-
-        else if type is 'time'
-            _parseTime = (time) ->
-                hour = parseInt(/^(\d+)/.exec(time)[1], 10)
-                minutes = /:(\d+)/.exec(time)[1]
-                ampm = /(am|pm|AM|PM)$/.exec(time)[1].toLowerCase()
-
-                if ampm is 'am'
-                    hour = hour.toString()
-
-                    if hour is '12'
-                        hour = '0'
-                    else if hour.length is 1
-                        hour = "0#{hour}"
-
-                    "#{hour}:#{minutes}"
-
-                else if ampm is 'pm'
-                    "#{hour + 12}:#{minutes}"
-
-            _sortAsc = (a, b) ->
-                a = _parseTime _patterns.time.exec($.trim($(a).find('td').eq(columnNum).text()))
-                b = _parseTime _patterns.time.exec($.trim($(b).find('td').eq(columnNum).text()))
-                new Date("04-22-2014 #{a}") - new Date("04-22-2014 #{b}")
-
-            _sortDesc = (a, b) ->
-                a = _parseTime _patterns.time.exec($.trim($(a).find('td').eq(columnNum).text()))
-                b = _parseTime _patterns.time.exec($.trim($(b).find('td').eq(columnNum).text()))
-                new Date("04-22-2014 #{b}") - new Date("04-22-2014 #{a}")
-
-            if direction is 'ascending' then rows.sort _sortAsc else rows.sort _sortDesc
-
-        else if type is 'alpha'
-            _cleanAlpha = (value) ->
-                # removes leading 'the' or 'a'
-                value.replace(/^the /i, '').replace /^a /i, ''
-
-            _sortAsc = (a, b) ->
-                # use clean alpha to remove leading 'the' or 'a' then convert to lowercase for case insensitive sort
-                a = _cleanAlpha($.trim($(a).find('td').eq(columnNum).text())).toLowerCase()
-                b = _cleanAlpha($.trim($(b).find('td').eq(columnNum).text())).toLowerCase()
-
-                if a < b
-                    -1
-                else if a > b
-                    1
-                else if a is b
-                    0
-
-            _sortDesc = (a, b) ->
-                # use clean alpha to remove leading 'the' or 'a' then convert to lowercase for case insensitive sort
-                a = _cleanAlpha($.trim($(a).find('td').eq(columnNum).text())).toLowerCase()
-                b = _cleanAlpha($.trim($(b).find('td').eq(columnNum).text())).toLowerCase()
-
-                if a < b
-                    1
-                else if a > b
-                    -1
-                else if a is b
-                    0
-
-            if direction is 'ascending' then rows.sort _sortAsc else rows.sort _sortDesc
-
-        else if type is 'number'
-            _sortAsc = (a, b) ->
-                parseFloat($.trim($(a).find('td').eq(columnNum).text())) - parseFloat($.trim($(b).find('td').eq(columnNum).text()))
-
-            _sortDesc = (a, b) ->
-                parseFloat($.trim($(b).find('td').eq(columnNum).text())) - parseFloat($.trim($(a).find('td').eq(columnNum).text()))
-
-            if direction is 'ascending' then rows.sort _sortAsc else rows.sort _sortDesc
-
-    renderTable: (direction, columnNum) =>
-        sortedRows = @sortRows direction, columnNum
-        tableBody = @table.find('tbody').empty()
-
-        $.each sortedRows, (key, value) ->
-            tableBody.append value
+                    return if direction is 'ascending' then a - b else b - a
+
+            getDataTypes: (listItems, columnNum, type = null) ->
+                self = @
+                values = self.getValues listItems, columnNum
+                types = []
+
+                if type?
+                    types.push type
+                else
+                    $.each values, ->
+                        if dataTypeChecks.isDate.call self, @
+                            types.push 'date'
+                        else if dataTypeChecks.isTime.call self, @
+                            types.push 'time'
+                        else if dataTypeChecks.isNumber.call self, @
+                            types.push 'number'
+                        else if dataTypeChecks.isAlpha.call self, @
+                            types.push 'alpha'
+                        else
+                            types.push null
+
+                return $.unique types
+
+            createArrays: (obj, list) ->
+                # create keys with empty arrays for each value in an array
+                $.each list, ->
+                    obj[@] = []
+                    return
+                return obj
+
+            concatArrays: (obj) ->
+                # combine an object made up of arrays into a single array
+                arr = []
+                $.each obj, ->
+                    arr = arr.concat @
+                    return
+                return arr
+
+            sortComplexList: (types, listItems, direction, columnNum) ->
+                # sort complex list with two or more data types
+                # group data types together
+                self = @
+                sortLists = {}
+                # create sortLists arrays
+                @createArrays sortLists, types
+                # add listItems to sortLists arrays
+                $.each listItems, ->
+                    listItem = @
+                    value = $.trim $(listItem).text()
+                    $.each types, ->
+                        if dataTypeChecks["is#{self.capitalize(@)}"].call self, value
+                            sortLists["#{@}"].push listItem
+                # sort sortLists arrays
+                $.each sortLists, (key) ->
+                    comparators["sort#{self.capitalize(key)}"] sortLists[key], direction, columnNum
+
+                return @concatArrays sortLists
+
+        dataTypeChecks =
+            isDate: (value) -> return if patterns.monthDayYear.test(value) then true else false
+            isNumber: (value) -> return if patterns.number.test(value) then true else false
+            isAlpha: (value) -> return if patterns.alpha.test(value) then true else false
+            isTime: (value) -> return if patterns.time.test(value) then true else false
+
+        comparators =
+            sortDate: (listItems, direction, columnNum) ->
+                # need support for various date and time formats
+                _sort = (a, b) ->
+                    a = $.trim $(a).find('td').eq(columnNum).text()
+                    b = $.trim $(b).find('td').eq(columnNum).text()
+                    if dataTypeChecks.isDate(a) and dataTypeChecks.isDate(b)
+                        a = new Date patterns.monthDayYear.exec(a)
+                        b = new Date patterns.monthDayYear.exec(b)
+
+                    return sortUtilities.sortValues a, b, direction
+
+                return listItems.sort _sort
+
+            sortTime: (listItems, direction, columnNum) ->
+                # need support for various date and time formats
+                _sort = (a, b) ->
+                    a = $.trim $(a).find('td').eq(columnNum).text()
+                    b = $.trim $(b).find('td').eq(columnNum).text()
+                    if dataTypeChecks.isTime(a) and dataTypeChecks.isTime(b)
+                        a = new Date "04-22-2014 #{sortUtilities.parseTime(patterns.time.exec(a))}"
+                        b = new Date "04-22-2014 #{sortUtilities.parseTime(patterns.time.exec(b))}"
+
+                    return sortUtilities.sortValues a, b, direction
+
+                return listItems.sort _sort
+
+            sortAlpha: (listItems, direction, columnNum) =>
+                _sort = (a, b) =>
+                    a = sortUtilities.cleanAlpha($.trim($(a).find('td').eq(columnNum).text()), @ignoreWords).toLowerCase()
+                    b = sortUtilities.cleanAlpha($.trim($(b).find('td').eq(columnNum).text()), @ignoreWords).toLowerCase()
+
+                    return sortUtilities.sortValues a, b, direction
+
+                return listItems.sort _sort
+
+            sortNumber: (listItems, direction, columnNum) ->
+                _sort = (a, b) ->
+                    a = parseFloat($.trim($(a).find('td').eq(columnNum).text()))
+                    b = parseFloat($.trim($(b).find('td').eq(columnNum).text()))
+
+                    return sortUtilities.sortValues a, b, direction
+
+                return listItems.sort _sort
+
+        type = @list.find('th').eq(columnNum).data 'type'
+        types = sortUtilities.getDataTypes listItems, columnNum, type
+        return sortUtilities.sortComplexList types, listItems, direction, columnNum
+
+    renderSort: (sortedRows, list) =>
+        list.empty()
+        $.each sortedRows, -> list.append @

 new DrmTableSorter()


Implemented additional methos of Document class

--- src/editor/document.coffee
+++ src/editor/document.coffee
@@ -10,14 +10,23 @@
     document.body.appendChild(@container)

   finalize: ->
+    return if @container is null
     document.body.removeChild(@container)
+    @container = null

   pageAt: (index) ->
+    return null if @container is null
     sections = @container.getElementsByTagName('section')
-    return null if sections.length <= index
+    length = sections.length
+    return null if index < 0 or length <= index
     return sections[index]

+  pageCount: (index) ->
+    return null if @container is null
+    return @container.getElementsByTagName('section').length
+
   toHtmlText: ->
+    return null if @container is null
     headContents = ''
     bodyContents = ''
     for c in @container.children
@@ -31,8 +40,20 @@
       <body>#{bodyContents}</body>
     </html>"

+  deletePage: (index) ->
+    return null if @container is null
+
+  addPage: (to) ->
+    return null if @container is null
+    to = Math.max(to, 0)
+    to = Math.min(to, @pageCount() - 1)
+    newElement = document.createElement('section')
+    @container.insertBefore(newElement, @pageAt(to))
+
   movePage: (from, to) ->
-
-  deletePage: (index) ->
-
-  addPage: (before) ->
+    return null if @container is null
+    to = Math.max(to, 0)
+    to = Math.min(to, @pageCount() - 1)
+    section = @pageAt(from)
+    @container.removeChild(section)
+    @container.insertBefore(section, @pageAt(to))


ComputeEventListener: ignore not existing events

--- client/Main/providers/computeeventlistener.coffee
+++ client/Main/providers/computeeventlistener.coffee
@@ -86,7 +86,9 @@
       activeListeners = []
       responses.forEach (res)=>

-        warn "Error on '#{res.event_id}':", res.err  if res.err?
+        if res.err? and not res.event?
+          warn "Error on '#{res.event_id}':", res.err
+          return

         [type, eventId] = res.event.eventId.split '-'

@@ -110,5 +112,6 @@
     .catch (err)=>

       @tickInProgress = no
+
       warn "Eventer error:", err
       @stop()


global settings menu delay fix

global settings menu delay fix

--- client/app/MainApp/maincontroller/mainview.coffee
+++ client/app/MainApp/maincontroller/mainview.coffee
@@ -110,6 +110,7 @@
       appController = KD.getSingleton "kodingAppsController"
       frontApp = appManager.getFrontApp()
       frontAppName = name for name, instances of appManager.appControllers when frontApp in instances
+      console.log frontAppName
       appController.constructor.manifests?[frontAppName]

     @mainSettingsMenuButton = new KDButtonView
@@ -147,9 +148,9 @@
       tabHandleContainer : @mainTabHandleHolder
     ,null

-    @mainTabView.on "PaneDidShow", =>
+    @mainTabView.on "PaneDidShow", => KD.utils.wait 100, =>
       appManifest = getFrontAppManifest()
-      @mainSettingsMenuButton.$()[if appManifest?.menu then "fadeIn" else "fadeOut"]()
+      @mainSettingsMenuButton[if appManifest?.menu then "show" else "hide"]()

     mainController = @getSingleton('mainController')
     mainController.popupController = new VideoPopupController


function that tests it can get the right reference where the original value is present. a copy of the original function with changes for testing. see your console log.

--- bootstrap-magic.coffee
+++ bootstrap-magic.coffee
@@ -63,7 +63,7 @@
   obj.isOverride = false
   obj.isReference = false

-  myVal = BootstrapMagic.dictionary.overrides.get(obj._id)
+  myVal = BootstrapMagic.dictionary.overrides.find(_id)
   if myVal
     obj.isOverride = true
   else
@@ -82,13 +82,30 @@

   return obj

+mapVariableResponsible = (obj) ->
+  obj.isOverride = false
+  obj.isReference = false
+
+  myVal = BootstrapMagic.dictionary.overrides.get(obj._id)
+
+  if obj.value?.indexOf('@') > -1
+    obj.isReference = true
+    obj.reference = mapVariableResponsible {_id: obj.value}
+    console.log "#{obj._id} comes from: #{obj.value}"
+    obj.reference.value?= '?'
+    if obj.reference.reference
+      obj.reference = obj.reference.reference
+
+  return obj
+
 camelToSnake = (str) -> str.replace(/\W+/g, '_').replace(/([a-z\d])([A-Z])/g, '$1-$2')

 Template._bootstrap_magic.helpers
   "categories" : ->  _.map _.groupBy(bootstrap_magic_variables, 'category'), (obj) ->  obj[0]
   "subCategories" : getCurrentCategory
   "currentSubCat" : getCurrentSubCategory
-  "mappedVariables" : -> _.map @data, mapVariableOverrides
+  # "mappedVariables" : -> _.map @data, mapVariableOverrides
+  "mappedVariables" : -> _.map @data, mapVariableResponsible
   "isSelectedCat" : -> @category is BootstrapMagic.dictionary.currentCategory.get()
   "isSelectedSubCat" : ->  @_id is BootstrapMagic.dictionary.currentSubCategory.get()
   "previewTmpl" : -> Template["bootstrap_magic_preview_#{camelToSnake @_id}"] || null


:sparkles: fixed debugger bug

:sparkles: fixed debugger bug

--- jThree/debug/components/jthree/jthree-preview-controller.coffee
+++ jThree/debug/components/jthree/jthree-preview-controller.coffee
@@ -2,7 +2,7 @@
   @loadedScriptCount = 0;

   @initJThree:(config,codeKey)->
-   JThreePreviewController.loadj3Script config,codeKey,()->
+   JThreePreviewController.loadj3Script config,codeKey,(codeKey)->
      JThreePreviewController.checkFinalize config,codeKey
      for v in config.codes[codeKey].js
        JThreePreviewController.loadjsScript config,config.config.root+v,()->
@@ -14,14 +14,16 @@
         j3Tag.setAttribute('x-goml',config.config.root+config.codes[codeKey].goml);
     else
       for k,v of config.codes
-        j3Tag.setAttribute('x-goml',v.goml)
+        codeKey = k
+        j3Tag.setAttribute('x-goml',config.config.root+v.goml)
         break
     j3Tag.setAttribute('type','text/javascript');
     j3Tag.setAttribute 'src',config.config.j3
     j3Tag.setAttribute 'x-lateLoad','true'
     j3Tag.onload = ()->
-      finished();
+      finished(codeKey);
     document.body.appendChild j3Tag
+    codeKey

   @loadjsScript:(config,url,finished)->
     jsTag = document.createElement 'script'


ComputeController: helpers to find stacks/machines from remote data

We'll use these helpers to find related stacks/machines from provided
remote repository data such as commitId, branch etc.

--- client/app/lib/providers/computecontroller.coffee
+++ client/app/lib/providers/computecontroller.coffee
@@ -291,6 +291,19 @@
     remote.api.JMachine.some query, (err, machines) ->
       if showError err then callback err
       else callback null, (new Machine { machine } for machine in machines)
+
+
+  findStackFromRemoteData: (options) ->
+
+    { commitId } = options
+    for stack in @stacks when _commitId = stack.config?.remoteDetails?.commitId
+      return stack  if ///^#{commitId}///.test _commitId
+
+
+  findMachineFromRemoteData: (options) ->
+
+    return  unless stack = @findStackFromRemoteData options
+    return  stack.machines?.first

   findMachineFromMachineId: (machineId) ->


Count non-active requests towards request totals on dashboard page

--- SingularityUI/app/collections/Requests.coffee
+++ SingularityUI/app/collections/Requests.coffee
@@ -88,8 +88,6 @@

             type = request.get 'type'

-            continue if request.get('state') isnt 'ACTIVE'
-
             if type is 'ON_DEMAND'  then userRequestTotals.onDemand  += 1
             if type is 'SCHEDULED'  then userRequestTotals.scheduled += 1
             if type is 'WORKER'     then userRequestTotals.worker    += 1


teamwork: fix `TeamworkApp::setVMRoot` by calling `NFinderController::updateVMRoot`

--- client/Teamwork/Views/teamworkapp.coffee
+++ client/Teamwork/Views/teamworkapp.coffee
@@ -130,11 +130,7 @@
     {finderController} = @teamwork.getActivePanel().getPaneByName "finder"

     cb = (vmName) ->
-
-      if finderController.getVmNode vmName
-        finderController.unmountVm vmName
-
-      finderController.mountVm "#{vmName}:#{path}"
+      finderController.updateVMRoot vmName, path

     vmController = KD.getSingleton "vmController"
     {defaultVmName} = vmController


change cookie set procedure

change cookie set procedure

--- JSKit/01_JSUserCookies.coffee
+++ JSKit/01_JSUserCookies.coffee
@@ -8,7 +8,7 @@
         super()

     setObject:(value, forKey)->
-        $.cookie forKey, encodeURIComponent(value), {expires:365}
+        $.cookie forKey, value, {expires:365}

     stringForKey:(forKey)->
         ret = decodeURIComponent($.cookie(forKey))


Remove unneeded subscription at the wish page (if the data already exists)

--- client/controller/wish-controller.coffee
+++ client/controller/wish-controller.coffee
@@ -1,7 +1,12 @@
 class @WishController extends RouteController

   waitOn: ->
-    [Meteor.subscribe('wish', @params.id), subscriptionHandles['users']]
+    subs = [subscriptionHandles.users]
+    if not Wishes.findOne({_id: @params.id})
+      s = Meteor.subscribe('wish', @params.id)
+      s.stop = ->
+      subs.push(s)
+    subs

   tempalte: 'wish'


add tooltip functionality

add tooltip functionality

--- client/lanes/components/shared/Icon.cjsx
+++ client/lanes/components/shared/Icon.cjsx
@@ -1,21 +1,48 @@
+DEFAULT_TOOLTIP_PROPS = { placement: 'top', trigger: 'click' }
+
 class Lanes.Components.Icon extends Lanes.React.BaseComponent

     propTypes:
-        type: React.PropTypes.string.isRequired
-        animated: React.PropTypes.bool
+        type:         React.PropTypes.string.isRequired
+        animated:     React.PropTypes.bool
+        spinner:      React.PropTypes.bool
+        className:    React.PropTypes.string
+        tooltip:      React.PropTypes.oneOfType([
+            React.PropTypes.string, React.PropTypes.element
+        ])
+        tooltipProps: React.PropTypes.object
+
+    getInitialState: ->
+        uniqueId: _.uniqueId('icon-tooltip-')
+
+    getDefaultProps: ->
+        tooltipProps: DEFAULT_TOOLTIP_PROPS

     render: ->
-
         classes = _.classnames 'icon', "icon-#{@props.type}", @props.className,
             'non-printable': @props.noPrint,
             'with-action' : @props.onClick,
             "icon-#{@props.size}" : @props.size,
             'icon-pulse' : @props.animated
+            'icon-spin'  : @props.spin or @props.type is 'spinner'
             'flush'      : @props.flush
             'icon-lg'    : @props['lg']
             'icon-2x'    : @props['2x']
             'icon-3x'    : @props['3x']
             'icon-4x'    : @props['4x']
             'icon-5x'    : @props['5x']
+            'clickable'  : @props.tooltip and @props.tooltipProps.trigger is 'click'

-        <i {...@props} style={@props.style} className={classes} />
+        icon =
+            <i {...@props} style={@props.style} className={classes} />
+
+        if @props.tooltip
+            props = _.extend({}, @props.tooltipProps, DEFAULT_TOOLTIP_PROPS)
+            <LC.Tooltip id={@state.uniqueId}
+                content={@props.tooltip}
+                {...props}
+            >
+                {icon}
+            </LC.Tooltip>
+        else
+            icon


Remove ng things.

Remove ng things.

--- lib/Watchers/Template.coffee
+++ lib/Watchers/Template.coffee
@@ -18,34 +18,18 @@
     getShortPath: (path)->
         types = Object.keys(TemplateWatcher.renderers)
         @pathpart(path)
-        .substr(1)
+        .replace(/^\//, '')
         .replace(///\.(?:#{types.join('|')})$///, '')
         .replace(/\\/g, '/') # Normalize pathing.
         .replace(/\/(?:[^\/]+-)?template$/, '')
-

     render: (code, path)->
         extension = path.substr(path.lastIndexOf('.') + 1)
         content = TemplateWatcher.renderers[extension].call(this, code, path)
         @wrap(path, content, code)

-    getModuleName: (shortPath)->
-        module = shortPath.replace(/\//g, '.') + '.template'
-        if moduleRoot = @config.templates.baseModule
-            module = "#{moduleRoot}.#{module}"
-        module
     cache: (path)->
         shortPath = @getShortPath path
-        module = @getModuleName shortPath
-
-        pre =[
-            "angular.module(", "'", module, "'", ", ", "[]", ")",
-            ".run(function($templateCache){"
-            "$templateCache.put(", "'", shortPath, "'", ",", " '"
-        ]
-        post = [
-            "');", "});"
-        ]

         pre = [
             "Templates", "[", "'", shortPath, "'", "]", " ", "=", " ", "'"


fixes

fixes

--- lib/browser-plus-view.coffee
+++ lib/browser-plus-view.coffee
@@ -99,10 +99,10 @@
       select = (event,ui)=>
         @goToUrl(ui.item.value)

-      jQ(@url).autocomplete
+      jQ(@url).autocomplete?(
           source: src
           minLength: 2
-          select: select
+          select: select)
       @subscriptions.add atom.tooltips.add @back, title: 'Back'
       @subscriptions.add atom.tooltips.add @forward, title: 'Forward'
       @subscriptions.add atom.tooltips.add @refresh, title: 'Refresh'
@@ -375,7 +375,7 @@
       @model.setTitle(null)
       @model.updateIcon(null)
       if url.startsWith('browser-plus://')
-        url = @model.browserPlus.getBrowserPlusUrl(url)
+        url = @model.browserPlus.getBrowserPlusUrl?(url)
       @htmlv.attr 'src',url

   showDevTool: (evt)->


Inline compile cache path

Inline compile cache path

--- src/less-compile-cache.coffee
+++ src/less-compile-cache.coffee
@@ -8,7 +8,7 @@
 class LessCompileCache
   Subscriber.includeInto(this)

-  @cacheDir: path.join(require('./coffee-cache').cacheDir, 'less')
+  @cacheDir: path.join(process.env.ATOM_HOME, 'compile-cache', 'less')

   constructor: ({resourcePath, importPaths}) ->
     @lessSearchPaths = [


Port slow test to bluebird

Port slow test to bluebird

--- server/test/units/controllers/theme.coffee
+++ server/test/units/controllers/theme.coffee
@@ -1,9 +1,11 @@
 assert = require('chai').assert
 helpers = require '../../helpers'
 async = require('async')
+Promise = require('bluebird')
 Q = require('q')
 _ = require('underscore')
 sinon = require('sinon')
+
 Theme = require('../../../models/theme').model
 Indicator = require('../../../models/indicator').model
 ThemeController = require('../../../controllers/themes')
@@ -16,6 +18,7 @@
 test(".index given no DPSIR parameters it returns all indicators
  and an DPSIR object with everything enabled", (done) ->
   theme = new Theme(title: 'test theme')
+
   driverIndicator = new Indicator(
     theme: theme._id
     dpsir: driver: true
@@ -32,13 +35,12 @@
     Q.fcall(->)
   )

-  Q.nsend(
-    theme, 'save'
-  ).then(->
-    Q.nsend(driverIndicator, 'save')
-  ).then(->
-    Q.nsend(pressureIndicator, 'save')
-  ).then(->
+  Promise.join(
+    Promise.promisify(theme.save, theme)(),
+    Promise.promisify(driverIndicator.save, driverIndicator)(),
+    Promise.promisify(pressureIndicator.save, pressureIndicator)()
+  ).then(->
+
     stubReq = {}
     stubRes = {
       send: (code, body) ->
@@ -74,7 +76,7 @@
       filterIndicatorsWithDataStub.restore()
       done(err)

-  ).fail( (err) ->
+  ).catch( (err) ->
     filterIndicatorsWithDataStub.restore()
     done(err)
   )


Trims excessively long vows strings in quickVows.

--- test/helpers/quick-vows.coffee
+++ test/helpers/quick-vows.coffee
@@ -41,7 +41,10 @@
       if vow is 'topic'
         context.topic = arg
       else
-        context["#{vow} '#{arg}'"] = (topic) ->
+        argString = "#{arg}"
+        argString = argString.substr(0, 20) + '...' if argString.length > 30
+        argString = argString.replace(/\n*/g, '')
+        context["#{vow} '#{argString}'"] = (topic) ->
           obj = topic
           scope = {}
           for part in vow.split(' ')


Fixed a bug not showing camera next photo date

--- src/scripts/build.coffee
+++ src/scripts/build.coffee
@@ -87,7 +87,7 @@

#{ title }

 			"""

-	if data.cameraDate is '1'
+	if data.cameraDate is 1
 		html += "#{ data.sysdate }"
 	else
 		html += "#{ data.sysdate }"


Removed legacy modal-system build fn

Removed legacy modal-system build fn

--- src/scripts/build.coffee
+++ src/scripts/build.coffee
@@ -146,35 +146,6 @@
 		when 'cog' then			html += "
No configuration
"

 	html += "
"
-
-	return html
-
-build.modal = (title, text, button, marginTop, closeButton) ->
-
-	if marginTop? then	custom_style = "style='margin-top: #{ marginTop }px;'"
-	else				custom_style = ''
-
-	html =	"""
-

-

-
#{ title }

-			"""
-
-	if closeButton isnt false then html += ""
-
-	html += "
#{ text }
"
-
-	$.each button, (index) ->
-
-		if this[0] isnt ''
-
-			if index is 0 then	html += "#{ this[0] }"
-			else				html += "#{ this[0] }"
-
-	html +=	"""
-

-

-			"""

 	return html


Update tweet.coffee URL format

Update tweet.coffee URL format

--- src/scripts/tweet.coffee
+++ src/scripts/tweet.coffee
@@ -64,6 +64,6 @@

         if data.statuses? and data.statuses.length > 0
           status = msg.random data.statuses
-          msg.send "http://twitter.com/#!/#{status.user.screen_name}/status/#{status.id_str}"
+          msg.send "https://twitter.com/#{status.user.screen_name}/status/#{status.id_str}"
         else
           msg.reply "No one is tweeting about that."


Update analytics traits

Update analytics traits

Adding school name and course prepaid info.

--- app/core/Tracker.coffee
+++ app/core/Tracker.coffee
@@ -62,7 +62,7 @@
     @explicitTraits ?= {}
     @explicitTraits[key] = value for key, value of traits

-    for userTrait in ['email', 'anonymous', 'dateCreated', 'name', 'testGroupNumber', 'gender', 'lastLevel', 'siteref', 'ageRange']
+    for userTrait in ['email', 'anonymous', 'dateCreated', 'name', 'testGroupNumber', 'gender', 'lastLevel', 'siteref', 'ageRange', 'schoolName', 'coursePrepaidID']
       traits[userTrait] ?= me.get(userTrait)
     console.log 'Would identify', me.id, traits if debugAnalytics
     return unless @isProduction and not me.isAdmin()


Added test for focus editor

Added test for focus editor

--- spec/coffee-compile-spec.coffee
+++ spec/coffee-compile-spec.coffee
@@ -56,12 +56,6 @@

   describe "open a new pane", ->
     beforeEach ->
-      waitsForPromise "coffee-compile package to activate", ->
-        atom.packages.activatePackage('coffee-compile')
-
-      waitsForPromise "fixture file to open", ->
-        atom.workspace.open "coffee-compile-fixtures.coffee"
-
       runs ->
         atom.workspaceView.getActiveView().trigger "coffee-compile:compile"

@@ -82,23 +76,17 @@
         [editorPane, compiledPane] = atom.workspaceView.getPaneViews()
         expect(compiledPane).toHaveFocus()

-  xdescribe "focus editor after compile", ->
+  describe "focus editor after compile", ->
     beforeEach ->
       atom.config.set "coffee-compile.focusEditorAfterCompile", true

-      waitsForPromise "coffee-compile package to activate", ->
-        atom.packages.activatePackage('coffee-compile')
-
-      waitsForPromise ->
-        atom.workspace.open "test.coffee"
-
+    it "should focus editor when option is set", ->
       runs ->
         atom.workspaceView.getActiveView().trigger "coffee-compile:compile"

       waitsFor ->
         CoffeeCompileView::renderCompiled.callCount > 0

-    xit "should focus editor when option is set", ->
       runs ->
         [editorPane, compiledPane] = atom.workspaceView.getPaneViews()
         expect(editorPane).toHaveFocus()


Update de-DE.coffee

Update de-DE.coffee

--- app/locale/de-DE.coffee
+++ app/locale/de-DE.coffee
@@ -1461,8 +1461,8 @@
     progress_journal_desc: "Halte deine Schüler an, ihren Fortschritt mithilfe eines Fortschrittstagebuchs aufzuzeichnen."
     cs1_curriculum: "Einführung in die Computerwissenschaft – Curriculumsleitfaden"
     cs1_curriculum_desc: "Umfang und Reihenfolge, Unterrichtspläne und mehr für Kurs 1."
-#    arenas_curriculum: "Arena Levels - Teacher Guide"
-#    arenas_curriculum_desc: "Instructions on how to run Wakka Maul, Cross Bones and Power Peak multiplayer arenas with your class."
+    arenas_curriculum: "Arena Level - Teacher Guide"
+    arenas_curriculum_desc: "Anleitung zum Betreiben von Wakka Maul, Cross Bones and Power Peak Multiplayer-Arenen mit deiner Klasse."
     cs2_curriculum: "Computerwissenschaft 2 – Curriculumsleitfaden"
     cs2_curriculum_desc: "Umfang und Reihenfolge, Unterrichtspläne und mehr für Kurs 2."
     cs3_curriculum: "Computerwissenschaft 3 – Curriculumsleitfaden"


One more change from #3483

One more change from #3483

--- app/locale/nl-NL.coffee
+++ app/locale/nl-NL.coffee
@@ -1032,7 +1032,7 @@
     play_now_learn_2: "while loops om lastige puzzels op te lossen"
     play_now_learn_3: "strings & variabelen om acties te aan te passen"
     play_now_learn_4: "hoe versla je een ogre (belangerijke levenslessen!)"
-    welcome_to_page: "Welkom op jhe cursus-pagina!"
+    welcome_to_page: "Welkom op je cursus-pagina!"
     completed_hoc: "Geweldig! Je hebt de Codeuur-cursus af!"
     ready_for_more_header: "Wil je meer? Speel de campagne-mode!"
     ready_for_more_1: "Gebruik edelstenen om nieuwe items te openen!"


Update pt-PT.coffee

Update pt-PT.coffee

--- app/locale/pt-PT.coffee
+++ app/locale/pt-PT.coffee
@@ -4,7 +4,7 @@
     no_ie: "O CodeCombat não funciona no Internet Explorer 8 ou anterior. Desculpa!" # Warning that only shows up in IE8 and older
     no_mobile: "O CodeCombat não foi feito para dispositivos móveis e pode não funcionar!" # Warning that shows up on mobile devices
     play: "Jogar" # The big play button that opens up the campaign view.
-#    play_campaign_version: "Play Campaign Version" # Shows up under big play button if you only play /courses
+    play_campaign_version: "Jogar Versão da Campanha" # Shows up under big play button if you only play /courses
     old_browser: "Uh oh, o teu navegador é demasiado antigo para que o CodeCombat funcione. Desculpa!" # Warning that shows up on really old Firefox/Chrome/Safari
     old_browser_suffix: "Podes tentar na mesma, mas provavelmente não vai funcionar."
     ipad_browser: "Más notícias: o CodeCombat não funciona no navegador do iPad. Boas notícias: a nossa aplicação nativa para iPad está à espera da aprovação da Apple."
@@ -78,7 +78,7 @@
     subscription_required: "Subscrição Necessária"
     anonymous: "Jogador Anónimo"
     level_difficulty: "Dificuldade: "
-#    play_classroom_version: "Play Classroom Version" # Choose a level in campaign version that you also can play in one of your courses
+    play_classroom_version: "Jogar Versão da Turma" # Choose a level in campaign version that you also can play in one of your courses
     campaign_beginner: "Campanha para Iniciantes"
     awaiting_levels_adventurer_prefix: "Lançamos novos níveis todas as semanas."
     awaiting_levels_adventurer: "Regista-te como Aventureiro"
@@ -118,9 +118,9 @@
     social_signup: "Ou podes registar-te através do FB ou do Google+:"
     required: "Precisas de iniciar sessão antes de prosseguires."
     login_switch: "Já tens uma conta?"
-#    school_name: "School Name and City"
-#    optional: "optional"
-#    school_name_placeholder: "Example High School, Springfield, IL"
+    school_name: "Nome e Cidade da Escola"
+    optional: "opcional"
+    school_name_placeholder: "Exemplo: Escola Secundária, Springfield, Illinois"

   recover:
     recover_account_title: "Recuperar Conta"
@@ -674,8 +674,8 @@
     being_reviewed_1: "A tua aplicação para uma avaliação gratuita está a ser"
     being_reviewed_2: "revista."
     approved_1: "A tua aplicação para uma avaliação gratuita foi"
-    approved_2: "aprovada." # {change}
-    approved_4: "Inscreve os teus alunos na" # {change}
+    approved_2: "aprovada!"
+    approved_4: "Agora podes inscrever os teus alunos na"
     approved_5: "página"
     approved_6: "dos cursos."
     denied_1: "A tua aplicação para uma avaliação gratuita foi"
@@ -860,24 +860,24 @@
     playtime: "Tempo de jogo"
     last_played: "Última vez jogado"
     leagues_explanation: "Joga numa liga contra outros membros do clã nestas instâncias de arenas multijogador."
-#    track_concepts1: "Track concepts"
-#    track_concepts2a: "learned by each student"
-#    track_concepts2b: "learned by each member"
-#    track_concepts3a: "Track levels completed for each student"
-#    track_concepts3b: "Track levels completed for each member"
-#    track_concepts4a: "See your students'"
-#    track_concepts4b: "See your members'"
-#    track_concepts5: "solutions"
-#    track_concepts6a: "Sort students by name or progress"
-#    track_concepts6b: "Sort members by name or progress"
-#    track_concepts7: "Requires invitation"
-#    track_concepts8: "to join"
-#    private_require_sub: "Private clans require a subscription to create or join."
+    track_concepts1: "Acompanhe os conceitos"
+    track_concepts2a: "aprendidos por cada estudante"
+    track_concepts2b: "aprendidos por cada membro"
+    track_concepts3a: "Acompanhe os níveis completados por cada estudante"
+    track_concepts3b: "Acompanhe os níveis completados por cada membro"
+    track_concepts4a: "Veja, dos seus alunos, as"
+    track_concepts4b: "Veja, dos seus membros, as"
+    track_concepts5: "soluções"
+    track_concepts6a: "Ordene os alunos por nome ou progresso"
+    track_concepts6b: "Ordene os membros por nome ou progresso"
+    track_concepts7: "É necessário um convite"
+    track_concepts8: "para se juntar"
+    private_require_sub: "É necessária uma subscrição para criar ou entrar num clã privado."

   courses:
     course: "Curso"
     courses: "cursos"
-#    create_new_class: "Create New Class"
+    create_new_class: "Criar Turma Nova"
     not_enrolled: "Não estás inscrito neste curso."
     visit_pref: "Por favor visita a página dos"
     visit_suf: "para te inscreveres."
@@ -1316,7 +1316,7 @@
     tutorial_skip: "Saltar Tutorial"
     tutorial_not_sure: "Não tens a certeza do que se passa?"
     tutorial_play_first: "Joga o Tutorial primeiro."
-    simple_ai: "IA Simples" # {change}
+    simple_ai: "CPU Simples"
     warmup: "Aquecimento"
     friends_playing: "Amigos a Jogar"
     log_in_for_friends: "Inicia sessão para jogares com os teus amigos!"
@@ -1338,8 +1338,8 @@
     rules: "Regras"
     winners: "Vencedores"
     league: "Liga"
-    red_ai: "IA Vermelha" # "Red AI Wins", at end of multiplayer match playback
-    blue_ai: "IA Azul" # {change}
+    red_ai: "CPU Vermelho" # "Red AI Wins", at end of multiplayer match playback
+    blue_ai: "CPU Azul"
     wins: "Vence" # At end of multiplayer match playback
     humans: "Vermelho" # Ladder page display team name
     ogres: "Azul"
@@ -1491,16 +1491,16 @@
     campaigns: "Campanhas"
     poll: "Votações"
     user_polls_record: "Histórico das Votações"
-#    course: "Course"
-#    courses: "Courses"
-#    course_instance: "Course Instance"
-#    course_instances: "Course Instances"
-#    classroom: "Classroom"
-#    classrooms: "Classrooms"
-#    clan: "Clan"
-#    clans: "Clans"
-#    members: "Members"
-#    users: "Users"
+    course: "Curso"
+    courses: "Cursos"
+    course_instance: "Instância do Curso"
+    course_instances: "Instâncias do Curso"
+    classroom: "Turma"
+    classrooms: "Turmas"
+    clan: "Clã"
+    clans: "Clãs"
+    members: "Membros"
+    users: "Utilizadores"

   concepts:
     advanced_strings: "'Strings' Avançadas"


Add keypress events for bubbles.

Add keypress events for bubbles.

--- app/scripts/main.coffee
+++ app/scripts/main.coffee
@@ -24,10 +24,16 @@
   bPaper = setupTimeline "b-timestream", bColors

   # streams
-  aClick = $('#button-a').asEventStream('click')
-  bClick = $('#button-b').asEventStream('click')
+  aClicks = $('#button-a').asEventStream('click')
+  bClicks = $('#button-b').asEventStream('click')
+  keydowns = $(document).asEventStream('keydown')
+  keycodeIs = (code) -> ((event) -> event.which is code)
+  aPresses = keydowns.filter(keycodeIs(65))
+  bPresses = keydowns.filter(keycodeIs(66))
+  aInputs = aClicks.merge(aPresses)
+  bInputs = bClicks.merge(bPresses)

   # side effects
-  aClick.map(["A", aPaper, aColors]).onValues(addBubble)
-  bClick.map(["B", bPaper, bColors]).onValues(addBubble)
+  aInputs.map(["A", aPaper, aColors]).onValues(addBubble)
+  bInputs.map(["B", bPaper, bColors]).onValues(addBubble)
 $(setup)


Defaulting test to pass

Defaulting test to pass

--- test/connects.io-uspec.coffee
+++ test/connects.io-uspec.coffee
@@ -1,5 +1,4 @@
 should  = require 'should'

 describe "connects.io", ->
-  it "is going to take the complex out of event processing for node...when it's written", ->
-    throw new Error "It's not written"+  it "is going to take the complex out of event processing for node...when it's written", ->


Add test for multiple input widget

Add test for multiple input widget

--- test_karma/test_widgets.coffee
+++ test_karma/test_widgets.coffee
@@ -326,6 +326,13 @@
       e.bubbles = true
       e

+    createInputEvent = ->
+      e = new Event "input"
+      e.bubbles = false
+      e.cancelable = false
+      e.type = "input"
+      e
+
     it "erases input value on clean by default", (done) ->
       controller = getControllerMock()
       widget = createWidget controller, "something"
@@ -398,6 +405,30 @@
         done()
       widget.value = "123"

+    it "can use multiple inputs", (done) ->
+      insertHTML """<input class="widget" type="text" value="something">
+                    <input class="widget" type="text" value="something">
+                    <input class="widget" type="text" value="something">
+                    <textarea class="widget">something</textarea>"""
+      controller = getControllerMock()
+      widget = new QueryInput ".widget", typingTimeout: 50
+      widget.setController controller
+      widget.init()
+
+      (widget.element.get 1).value = "something1"
+      (widget.element.get 1).dispatchEvent createInputEvent()
+      widget.value.should.equal "something1"
+      (widget.element.get 0).value = "something0"
+      (widget.element.get 0).dispatchEvent createInputEvent()
+      widget.value.should.equal "something0"
+      (widget.element.get 2).value = "something2"
+      (widget.element.get 2).dispatchEvent createInputEvent()
+      widget.value.should.equal "something2"
+      (widget.element.get 3).value = "something3"
+      (widget.element.get 3).dispatchEvent createInputEvent()
+      widget.value.should.equal "something3"
+      done()
+
     it "triggers a special event for enter key if input is not a textarea", (done) ->
       controller = getControllerMock()
       widget = createWidget controller, "something"


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/number_extensions.coffee
+++ test/number_extensions.coffee
@@ -110,6 +110,7 @@
   equals 0.5.turns, Math.TAU / 2

 test "#circularPoints", ->
+
   4.circularPoints (p) ->


Use skinny arrows in Emitter shims

Use skinny arrows in Emitter shims

--- src/atom.coffee
+++ src/atom.coffee
@@ -1,8 +1,8 @@
 #FIXME remove once all packages have been updated
 {Emitter} = require 'emissary'
-Emitter::one = (args...) => @once(args...)
-Emitter::trigger = (args...) => @emit(args...)
-Emitter::subscriptionCount = (args...) => @getSubscriptionCount(args...)
+Emitter::one = (args...) -> @once(args...)
+Emitter::trigger = (args...) -> @emit(args...)
+Emitter::subscriptionCount = (args...) -> @getSubscriptionCount(args...)

 fsUtils = require './fs-utils'
 $ = require './jquery-extensions'


[client][app][application] added header resetting

--- client/landing/app/Applications/Activity.kdapplication/ContentDisplays/Discussion.coffee
+++ client/landing/app/Applications/Activity.kdapplication/ContentDisplays/Discussion.coffee
@@ -37,10 +37,11 @@
       partial  : @opinionHeaderCountString data.opinionCount

     @opinionBox.opinionList.on "OwnOpinionHasArrived", (data)=>
-      @opinionBoxHeader.updatePartial @opinionHeaderCountString @getData().opinionCount
+      @resetHeaderCount data
+      @opinionBox.resetDecoration()

     @opinionBox.opinionList.on "OpinionIsDeleted", (data)=>
-      @opinionBoxHeader.updatePartial @opinionHeaderCountString @getData().opinionCount
+      @resetHeaderCount data

     @opinionForm = new OpinionFormView
       preview         :
@@ -170,7 +171,7 @@

           @opinionBox.opinionList.emit "NewOpinionHasArrived"

-        @opinionBoxHeader.updatePartial @opinionHeaderCountString data.opinionCount
+        @opinionBoxHeader.updatePartial @opinionHeaderCountString reply.opinionCount

     # When the activity gets deleted correctly, it will emit this event,
     # which leaves only the count of the custom element to be updated
@@ -203,6 +204,10 @@
           for comment in comments       # and add them to the commentBox
             @commentBox.commentList.addItem comment

+
+  resetHeaderCount:(data)->
+    opinionCount = @opinionBox.opinionList.items and @opinionBox.opinionList.items.length or 0
+    @opinionBoxHeader.updatePartial @opinionHeaderCountString opinionCount

   opinionHeaderCountString:(count)->


handle modules with js prototypes

handle modules with js prototypes

--- app/assets/javascripts/action_assets/string.js.coffee
+++ app/assets/javascripts/action_assets/string.js.coffee
@@ -6,5 +6,11 @@
     undefined

 String::camelize = ->
-    @replace /(?:^|[-_])(\w)/g, (_, c) ->
-      (if c then c.toUpperCase() else '')
+  @replace /(?:^|[-_/])(\w)/g, (s, c) ->
+    if c
+      if s.indexOf('/') == 0
+          ".prototype.#{c.toUpperCase()}"
+        else
+          c.toUpperCase()
+    else
+      ''


Make filter renderer not global.

Make filter renderer not global.


Raise error on failed action in flow.

Raise error on failed action in flow.

--- src/support/index.coffee
+++ src/support/index.coffee
@@ -10,7 +10,8 @@
   @_inFlow = (code, callback) ->
     $.createFlow (flow) =>
       flow.execute => code.call(@)
-    .then _.partial(callback, null), callback
+    .then _.partial(callback, null),
+      (err) -> throw err

   @Before = (code) ->
     _Before (callback) =>


Fix callbacks never being applied when out of sequence response is detected

--- app/assets/javascripts/models/data/datasets.js.coffee
+++ app/assets/javascripts/models/data/datasets.js.coffee
@@ -51,7 +51,7 @@
     _loadGranuleAccessOptions: ->
       params = @query.params()
       downloadableParams = extend(@_granuleParams(params), online_only: true, page_size: 2000)
-      @searchGranules params, (_, granulesModel) =>
+      new Granules().search @_granuleParams(params), (_, granulesModel) =>
         hits = granulesModel.hits()
         granulesModel = new Granules().search downloadableParams, (params, model) =>
           downloadableHits = model.hits()


Jetpack ciribot

Jetpack ciribot

--- src/entities.coffee
+++ src/entities.coffee
@@ -183,14 +183,16 @@
             return false
         return wouldCollide(@, 0, Math.max(1, @body.vel.y), me.collision.types.WORLD_SHAPE, -8)

-    jump: (ignoreCheck = false) ->
-        if ignoreCheck or @hasFloorBelow()
-            charge_percent = Math.max(Math.abs(@body.vel.x) - MIN_SPEED, 0) / (MAX_SPEED - MIN_SPEED)
-            # set current vel to the maximum defined value
-            # gravity will then do the rest
-            @body.vel.y = -(MIN_JUMP + (MAX_JUMP - MIN_JUMP) * charge_percent)
+    handleJump: () ->
+        if @controllingJump and @body.jumping and @body.vel.y < 0
+            @body.vel.y -= 1
+
+    jump: (amount, forceJump = false) ->
+        if forceJump or @hasFloorBelow()
+            @body.vel.y = amount
             # set the jumping flag
             @body.jumping = true
+        @controllingJump = not forceJump
     tryMakeBlock: (dx) ->
         [x, y] = [@getRx(), @getRy()]
         x += dx
@@ -246,12 +248,13 @@
         if me.input.isKeyPressed('down')
             @body.vel.x = 0

-        # holdingJump = me.input.isKeyPressed('jump')
         # # Jump on every 'edge'
-        if jumpReleased or me.input.isKeyPressed('jump') #holding_jump != holdingJump
-            @jump()
+        if jumpReleased
+            @controllingJump = false
+        if me.input.isKeyPressed('jump')
+            @jump(-5)
+        @handleJump()
         jumpReleased = false
-        # holding_jump = holdingJump
         @baseUpdate(dt)

     onCollision: (response, other) ->
@@ -263,7 +266,7 @@
                 if response.overlapV.y > 0 or (other.pos.y > @pos.y + 8)
                     other.die()
                     if !@body.jumping
-                        @jump(true)
+                        @jump(-8, true)
                         # play some audio
                         me.audio.play 'stomp'
                 else if (other instanceof game.Bullet) and (response.overlapV.y < 0 or (other.pos.y + 24 < @pos.y))


Layout cleanup

Layout cleanup

--- app/coffee/modules/main/dashboard/views/layout.coffee
+++ app/coffee/modules/main/dashboard/views/layout.coffee
@@ -21,14 +21,13 @@

   showControlsView: (keyModel) ->

-    # @controlsRegion.show new KeyEditor({ model: keyModel, keys: @options.keys })
-
     # Gets the current macro assigned to the keyModel
     # macroCollection = keyModel.getMacroCollection()
     @macroRegion.show new MacroList({ collection: @options.macros })

     # Instantaiates new KeyboardView
     # TODO - this will *eventually* display a selector between different types of keyboards / sets of keys
+    # @controlsRegion.show new KeyEditor({ model: keyModel, keys: @options.keys })
     keyboardView = new KeyboardView({ model: keyModel, keys: @options.keys })

     # Handles KeySelection event


fixed checkEncrypt so it fetches field value from model by path

--- lib/plugins/encrypted.coffee
+++ lib/plugins/encrypted.coffee
@@ -2,7 +2,7 @@

 module.exports = (schema) ->
     schema.methods.checkEncrypted = (fieldName, value, callback) ->
-        fieldValue = @[fieldName]
+        fieldValue = @get fieldName
         return callback?(null, false) if not fieldValue
         encryptionMethod = (fieldValue.split('$$')[0] or '').split(':')[0]
         Strategy = strategies[encryptionMethod]


Fix bug where toolbar reverts after preformat

Clicking on the toolbar element loses selection (esp dropdown since
it takes a second to make a selection) so the refocus on editor on
preformat triggers a selection change which triggers a toolbar active
update.

--- src/modules/toolbar.coffee
+++ src/modules/toolbar.coffee
@@ -16,6 +16,7 @@
     throw new Error('container required for toolbar', @options) unless @options.container?
     @container = if _.isString(@options.container) then document.querySelector(@options.container) else @options.container
     @activeFormats = {}
+    @preventUpdate = false
     _.each(@quill.options.formats, (format) =>
       return if Toolbar.formats.TOOLTIP[format]?
       eventName = if Toolbar.formats.SELECT[format]? then 'change' else 'click'
@@ -35,9 +36,11 @@
     return unless input?
     DOM.addEventListener(input, eventName, =>
       value = if eventName == 'change' then DOM.getSelectValue(input) else !DOM.hasClass(input, 'sc-active')
+      @preventUpdate = true
       @quill.focus()
       range = @quill.getSelection()
       callback(range, value) if range?
+      @preventUpdate = false
     )

   setActive: (format, value) ->
@@ -57,6 +60,7 @@
       DOM.toggleClass(input, 'sc-active', value)

   updateActive: (range) ->
+    return unless range? and !@preventUpdate
     activeFormats = this._getActive(range)
     _.each(_.keys(@activeFormats), (name) =>
       if activeFormats[name]?
@@ -69,16 +73,13 @@
     )

   _getActive: (range) ->
-    if range?
-      if range.isCollapsed()
-        start = Math.max(0, range.start - 1)
-        contents = @quill.getContents(start, range.end)
-      else
-        contents = @quill.getContents(range)
-      formatsArr = _.map(contents.ops, 'attributes')
-      return this._intersectFormats(formatsArr)
+    if range.isCollapsed()
+      start = Math.max(0, range.start - 1)
+      contents = @quill.getContents(start, range.end)
     else
-      return {}
+      contents = @quill.getContents(range)
+    formatsArr = _.map(contents.ops, 'attributes')
+    return this._intersectFormats(formatsArr)

   _intersectFormats: (formatsArr) ->
     return _.reduce(formatsArr.slice(1), (activeFormats, formats) ->


APNS error callback takes 2 parameters

APNS error callback takes 2 parameters

--- lib/pushservices/apns.coffee
+++ lib/pushservices/apns.coffee
@@ -7,8 +7,8 @@
             return token.toLowerCase()

     constructor: (conf, @logger, tokenResolver) ->
-        conf.errorCallback = (errCode, note, device) =>
-            @logger?.error("APNS Error #{errCode} for subscriber #{device?.subscriberId}")
+        conf.errorCallback = (errCode, note) =>
+            @logger?.error("APNS Error #{errCode}: #{note}")

         # The APN library decided to change the default version of those variables in 1.5.1
         # Maintain the previous defaults in order not to break backward compat.


Fixed a critical issue with loading traditional script files.

--- src-client/module_loader.coffee
+++ src-client/module_loader.coffee
@@ -35,7 +35,7 @@
   module.factory = factory
   inProgressModules[path] = module

-  if /\.(html|htm|json|css)$/.test(path)
+  if module.format is 'text'
     # If it's a text file wrapper, evaluate right away.
     module.exports = factory()
     delete module.factory
@@ -130,6 +130,15 @@

 # Load a module
 load = (path, callback) ->
+  if /\.(html|htm|json|css)$/.test(path)
+    format = 'text'
+  else if /\.js$/.test(path)
+    # If path ends with '.js', it's considered as a traditional script; otherwise a module.
+    format = 'script'
+    path = path[...-3]
+  else
+    format = 'module'
+
   # Skip if the module is already loaded
   if modules[path]
     callback(modules[path])
@@ -142,9 +151,9 @@
     return

   # Otherwise, fetch the module from its path
-  inProgressModules[path] = {path, callbacks: [callback]}
-
-  if /\.(html|htm|json|css)$/.test(path)
+  inProgressModules[path] = {path, format, callbacks: [callback]}
+
+  if format is 'text'
     fetchText path, (text) ->
       module = inProgressModules[path]
       module.exports = text
@@ -175,12 +184,6 @@

 didLoadModule = (module) ->
   path = module.path
-
-  # If it's a wrapped non-AMD script, evaluate it here.
-  # if /\.js$/.test(path) and module.deps
-  #   module.factory.call(window)
-  #   delete module.factory
-  #   module.exports = window[shim[path]?.exports]

   # Save the module in memory
   modules[path] = module
@@ -220,7 +223,10 @@
     for own prop, value of require
       localRequire[prop] = value

-    module.factory.call(window, localRequire, module.exports, module)
+    if module.format is 'module'
+      module.factory.call(window, localRequire, module.exports, module)
+    else
+      module.factory.call(window)
     delete module.factory
   return module.exports


Adds support for DI on EventSourceables

Adds support for DI on EventSourceables

--- source/server/infrastructure/router.coffee
+++ source/server/infrastructure/router.coffee
@@ -6,6 +6,7 @@
     configuration: 'configuration'
     repository: 'Space.eventSourcing.Repository'
     commitStore: 'Space.eventSourcing.CommitStore'
+    injector: 'Injector'
     log: 'log'
   }

@@ -54,13 +55,23 @@
       @eventBus.subscribeTo @initializingMessage, (event) =>
         @log.info("#{this}: Creating new #{@eventSourceable} with event
                   #{event.typeName()}\n", event)
-        eventSourceable = @_handleDomainErrors(-> new @eventSourceable event)
+        eventSourceable = @_handleDomainErrors(->
+          instance = new @eventSourceable(event.sourceId)
+          @_injectDependencies(instance)
+          instance.handle(event)
+          return instance
+        )
         @repository.save(eventSourceable) if eventSourceable?
     else if @initializingMessage.isSubclassOf(Space.domain.Command)
       @commandBus.registerHandler @initializingMessage, (cmd) =>
         @log.info("#{this}: Creating new #{@eventSourceable} with command
                   #{cmd.typeName()}\n", cmd)
-        eventSourceable = @_handleDomainErrors(-> new @eventSourceable cmd)
+        eventSourceable = @_handleDomainErrors(->
+          instance = new @eventSourceable(cmd.targetId)
+          @_injectDependencies(instance)
+          instance.handle(cmd)
+          return instance
+        )
         @repository.save(eventSourceable) if eventSourceable?

   _routeEventToEventSourceable: (eventType) ->
@@ -76,6 +87,7 @@
     @log.info(@_logMsg("Handling event #{event.typeName()} for
                        #{@eventSourceable}<#{correlationId}>\n"), event)
     eventSourceable = @repository.find @eventSourceable, correlationId
+    @_injectDependencies(eventSourceable)
     throw Router.ERRORS.cannotHandleMessage(event) if !eventSourceable?
     eventSourceable = @_handleDomainErrors(-> eventSourceable.handle event)
     @repository.save(eventSourceable) if eventSourceable?
@@ -85,9 +97,13 @@
     @log.info(@_logMsg("Handling command #{command.typeName()} for
                        #{@eventSourceable}<#{command.targetId}>"), command)
     eventSourceable = @repository.find @eventSourceable, command.targetId
+    @_injectDependencies(eventSourceable)
     throw Router.ERRORS.cannotHandleMessage(command) if !eventSourceable?
     eventSourceable = @_handleDomainErrors(-> eventSourceable.handle command)
     @repository.save(eventSourceable) if eventSourceable?
+
+  _injectDependencies: (eventSourceable) ->
+    @injector.injectInto(eventSourceable)

   _logMsg: (message) -> "#{@configuration.appId}: #{this}: #{message}"


Use alt instead of ctrl

Use alt instead of ctrl

--- keymaps/to-the-hubs.cson
+++ keymaps/to-the-hubs.cson
@@ -1,5 +1,5 @@
 '.workspace':
-  'ctrl-g o': 'github:open'
-  'ctrl-g b': 'github:blame'
-  'ctrl-g h': 'github:history'
-  'ctrl-g c': 'github:copy-url'
+  'alt-g o': 'github:open'
+  'alt-g b': 'github:blame'
+  'alt-g h': 'github:history'
+  'alt-g c': 'github:copy-url'


Tweaked index file require calls so it works with regular node and browserify.

--- src/fieldkit.coffee
+++ src/fieldkit.coffee
@@ -13,8 +13,7 @@
 # Namespace
 fk =
   # Merges the given source file or module into the main fk namespace
-  module: (source) ->
-    module = if typeof source == 'string' then require source else source
+  module: (module) ->
     util.extend this, module

   # Stores the given module in a named subpackage
@@ -27,13 +26,13 @@
 #

 # Math
-fk.module './math/vector'
-fk.module './math/random'
+fk.module require './math/vector'
+fk.module require './math/random'

 # Misc
-fk.module './color'
-fk.module './time'
+fk.module require './color'
+fk.module require './time'

 fk.package 'util', util

-module.exports = exports = fk
+module.exports = fk


Fixed joyride disable tests

Fixed joyride disable tests

--- app/modules/services/current-user.service.spec.coffee
+++ app/modules/services/current-user.service.spec.coffee
@@ -172,7 +172,16 @@

         expect(currentUserService._user).to.be.null

-    it "disable joyride", () ->
+    it "disable joyride for anon user", () ->
+        currentUserService.isAuthenticated = sinon.stub()
+        currentUserService.isAuthenticated.returns(false)
+        currentUserService.disableJoyRide()
+
+        expect(mocks.resources.user.setUserStorage).to.have.not.been.called
+
+    it "disable joyride for logged user", () ->
+        currentUserService.isAuthenticated = sinon.stub()
+        currentUserService.isAuthenticated.returns(true)
         currentUserService.disableJoyRide()

         expect(mocks.resources.user.setUserStorage).to.have.been.calledWith('joyride', {


Refactor slack-attachment usage

Refactor slack-attachment usage

Based on docs in hubot-slack-attachment, as well as what could
be pieced together from hubot-slack#148 &
hubot-slack#162, this _might_ fix things to avoid the dreaded
`ReferenceError: robot is not defined`

--- src/forecast.coffee
+++ src/forecast.coffee
@@ -328,17 +328,14 @@
         robot.brain.set(KV_KEY, dataPoint)

         if robot.adapterName == 'slack'
-          attachmentsObj = [ {
-            color: msgColor
-            title: 'Weather Update!'
-            text: msg
-            fallback: msg
-          } ]
           robot.emit 'slack-attachment',
             channel: room
-            content: attachmentsObj
-            username: (process.env.HUBOT_SLACK_BOTNAME ? 'hubot')
-            text: ''
+            content:
+              color: msgColor
+              title: 'Weather Update!'
+              text: msg
+              fallback: msg
+            message: ''
         else
           robot.messageRoom room, msg

@@ -360,17 +357,14 @@
     response += "\nToday: #{last_json.hourly.summary}"
     response += "\nComing week: #{last_json.daily.summary}"
     if robot.adapterName == 'slack'
-      attachmentsObj = [ {
-        color: '#000000'
-        title: 'Here is your weather report…'
-        text: response
-        fallback: response
-      } ]
       robot.emit 'slack-attachment',
         channel: msg.envelope.room
-        content: attachmentsObj
-        username: (process.env.HUBOT_SLACK_BOTNAME ? 'hubot')
-        text: ''
+        content:
+          color: '#000000'
+          title: 'Here is your weather report…'
+          text: response
+          fallback: response
+        message: ''
     else
       msg.send response


監視対象にSassを追加

監視対象にSassを追加


fixed problem with tmpFile write

fixed problem with tmpFile write

--- src/mist-ninja-builder.coffee
+++ src/mist-ninja-builder.coffee
@@ -150,7 +150,7 @@
       else
         tmpFile = tmp.fileSync()
         args.push '-f', tmpFile.name
-        fs.writeSync tmpFile.fd rendered
+        fs.writeSync tmpFile.fd, rendered
         fs.closeSync tmpFile.fd

     proc = spawn @ninjaProc, args, opts


fix handling of lead completer response

fix handling of lead completer response

--- src/graphite.coffee
+++ src/graphite.coffee
@@ -201,11 +201,11 @@
       msg ? 'Unknown error'

     parse_find_response: (query, response) ->
-      parts = query.split('.')
-      pattern_parts = parts.map(graphite.is_pattern)
+      parts = query.split '.'
+      pattern_parts = parts.map graphite.is_pattern
       list = (node.path for node in response)
       patterned_list = for path in list
-        result = for matched, i in path.split('.')
+        result = for matched, i in path.split '.'
           if pattern_parts[i]
             parts[i]
           else
@@ -233,7 +233,10 @@
     # returns a promise
     complete: (query) ->
       graphite.find(query + '*')
-      .then ({query, result}) ->
+      .then ({result}) ->
+        if settings.get('type') == 'lead'
+          for n in result
+            n.path += '.' unless n.is_leaf
         graphite.parse_find_response query, result

     find: (query) ->


Using _.map instead of _.pluck

Using _.map instead of _.pluck

--- framer/Extras/Preloader.coffee
+++ framer/Extras/Preloader.coffee
@@ -37,10 +37,10 @@
 		@brand.style["background-image"] = "url('#{url}')" if @brand

 	addImagesFromContext: (context) ->
-		_.pluck(context.layers, "image").map(@addImage)
+		_.map(context.layers, "image").map(@addImage)

 	addPlayersFromContext: (context) ->
-		_.pluck(context.layers, "player").map(@addPlayer)
+		_.map(context.layers, "player").map(@addPlayer)

 	addImage: (image) =>
 		if image and image not in @_media


update where method

update where method

--- lib/client/javascripts/nali/collection.js.coffee
+++ lib/client/javascripts/nali/collection.js.coffee
@@ -103,9 +103,8 @@
     @

   where: ( filters ) ->
-    result = []
-    result.push model for model in @ when model.isCorrect filters
-    result
+    filters[ name ] = value for name, value of @filters
+    @model.where filters

   order: ( @ordering ) ->
     @reorder()


start date picker, first draft

start date picker, first draft

--- client/app/views/konnector.coffee
+++ client/app/views/konnector.coffee
@@ -60,9 +60,16 @@
             fieldHtml += "<option value=\"#{key}\" #{selected}>#{value}</option>"

         fieldHtml += """
-</select>

-

- """
+</select>
+<select id="#{slug}-startimport-input" class="startimport">
+    """
+        startimport = {none: "Options"} ## Todo : list days of the week, and days of the month
+        for key, value of startimport
+            fieldHtml += "<option value=\"#{key}\" >#{value}</option>"
+
+        fieldHtml += """
+</select>

+"""
         @$('.fields').append fieldHtml

     onImportClicked: =>


Listview, improve infinite scroll.

Listview, improve infinite scroll.

--- client/app/views/list_view.coffee
+++ client/app/views/list_view.coffee
@@ -63,10 +63,10 @@
             @loadAfter @keepScreenFull # infinite loop end by @lastAlreadyLoaded

     checkScroll: =>
-        triggerPoint = 100 # 100px from the bottom
+        triggerPoint = 150 # 100px from the bottom
         list = @$('#list-container')[0]
         if list.scrollTop + list.clientHeight + triggerPoint > list.scrollHeight
-            @loadAfter()
+            @loadAfter @checkScroll

     loadBefore: (callback)->
         if not @isLoading


syntax: recognize numbers in scientific notation

--- client/codemirror-apl-mode.coffee
+++ client/codemirror-apl-mode.coffee
@@ -50,7 +50,7 @@
       'apl-tradfn'
     else if stream.match idiomsRE
       'apl-idiom'
-    else if stream.match /^¯?(?:\d*\.)?\d+(?:j¯?(?:\d*\.)?\d+)?/i
+    else if stream.match /^¯?(?:\d*\.)?\d+(?:e¯?\d+)?(?:j¯?(?:\d*\.)?\d+(?:e¯?\d+)?)?/i
       'apl-number'
     else
       c = stream.next()


Make / link link to root page

Make / link link to root page

--- client/components/Material.coffee
+++ client/components/Material.coffee
@@ -27,7 +27,8 @@
     <Breadcrumb>
         {
         props.path.map((p) ->
-            <LinkContainer to={"/material/" + p._id} key={p._id} isActive={() -> false}>
+            href = if p._id != "main" then "/material/" + p._id else "/"
+            <LinkContainer to={href} key={p._id} isActive={() -> false}>
                  <Breadcrumb.Item active={p._id==props.id}>
                     {p.title}
                 </Breadcrumb.Item>


Remove newline and redundent spaces from the terms suggested by Agda


experimentally meta-programming: derive A and Inner child classes


Add temporary special case for empty list

(So it is not considered a singleton with an empty element.)

--- lib/text-manipulation.coffee
+++ lib/text-manipulation.coffee
@@ -70,6 +70,9 @@

         elementRanges = @getElementRangesFromNonNested bufferText, listStartIx, listEndIx, nonNestedIxRanges

+        # Because empty elements are allowed, [\s*] will be interpreted as a list with single empty element
+        # TODO: For now, disallow this, as it requires some changes to the model to accomodate the whitespace in an empty list
+        elementRanges = [] if elementRanges.length == 1 and elementRanges[0].eltStart == elementRanges[0].eltEnd
         # @showIxRanges bufferText, elementRanges

         _.map elementRanges, (r) ->


removed unused dependencies in the github page

--- app/coffee/modules/admin/third-parties.coffee
+++ app/coffee/modules/admin/third-parties.coffee
@@ -16,14 +16,12 @@
 # You should have received a copy of the GNU Affero General Public License
 # along with this program. If not, see <http://www.gnu.org/licenses/>.
 #
-# File: modules/admin/memberships.coffee
+# File: modules/admin/third-parties.coffee
 ###

 taiga = @.taiga

 mixOf = @.taiga.mixOf
-bindOnce = @.taiga.bindOnce
-debounce = @.taiga.debounce

 module = angular.module("taigaAdmin")

@@ -38,12 +36,10 @@
         "$tgRepo",
         "$tgResources",
         "$routeParams",
-        "$q",
-        "$tgNavUrls",
         "$appTitle"
     ]

-    constructor: (@scope, @repo, @rs, @params, @q, @navUrls, @appTitle) ->
+    constructor: (@scope, @repo, @rs, @params, @appTitle) ->
         _.bindAll(@)

         @scope.sectionName = "Github" #i18n
@@ -94,7 +90,7 @@
 ## GithubWebhooks Directive
 #############################################################################

-GithubWebhooksDirective = ($repo, $confirm, $loading, $navurls, $location) ->
+GithubWebhooksDirective = ($repo, $confirm, $loading) ->
     link = ($scope, $el, $attrs) ->
         form = $el.find("form").checksley({"onlyOneErrorElement": true})
         submit = (target) =>
@@ -124,4 +120,4 @@

     return {link:link}

-module.directive("tgGithubWebhooks", ["$tgRepo", "$tgConfirm", "$tgLoading", "$tgNavUrls", "$tgLocation", GithubWebhooksDirective])
+module.directive("tgGithubWebhooks", ["$tgRepo", "$tgConfirm", "$tgLoading", GithubWebhooksDirective])


Track which packages are in the process of activating

This fixes a race condition where a package's activation promise resolves asynchronously
after it has been deactivated, causing it to stay in the package manager's @activePackages
object.

--- src/package-manager.coffee
+++ src/package-manager.coffee
@@ -47,6 +47,7 @@
     @packagesCache = require('../package.json')?._atomPackages ? {}
     @loadedPackages = {}
     @activePackages = {}
+    @activatingPackages = {}
     @packageStates = {}
     @serviceHub = new ServiceHub

@@ -437,9 +438,12 @@
     if pack = @getActivePackage(name)
       Promise.resolve(pack)
     else if pack = @loadPackage(name)
+      @activatingPackages[pack.name] = pack
       pack.activate().then =>
-        @activePackages[pack.name] = pack
-        @emitter.emit 'did-activate-package', pack
+        if @activatingPackages[pack.name]?
+          delete @activatingPackages[pack.name]
+          @activePackages[pack.name] = pack
+          @emitter.emit 'did-activate-package', pack
         pack
     else
       Promise.reject(new Error("Failed to load package '#{name}'"))
@@ -475,6 +479,7 @@
       @setPackageState(pack.name, state) if state = pack.serialize?()
     pack.deactivate()
     delete @activePackages[pack.name]
+    delete @activatingPackages[pack.name]
     @emitter.emit 'did-deactivate-package', pack

   handleMetadataError: (error, packagePath) ->


SCSS adapter improperly named

SCSS adapter improperly named

It's currently named as "CSS", which means you have to pass `node-sass` options to "css" and not "scss". This fixes that.

--- lib/adapters/scss.coffee
+++ lib/adapters/scss.coffee
@@ -3,7 +3,7 @@
 path       = require 'path'

 class SCSS extends Adapter
-  name: 'css'
+  name: 'scss'
   extensions: ['scss']
   output: 'css'
   supportedEngines: ['node-sass']


bugfix:  in the file browser, the “Showing only files that contain “ is often wrong (off by one last letter)

--- salvus/page/project.coffee
+++ salvus/page/project.coffee
@@ -443,12 +443,13 @@

     init_file_search: () =>
         @_file_search_box = @container.find(".salvus-project-search-for-file-input")
-        @_file_search_box.keydown (event) =>
+        @_file_search_box.keyup (event) =>
             if (event.metaKey or event.ctrlKey) and event.keyCode == 79
                 #console.log("keyup: init_file_search")
                 @display_tab("project-new-file")
                 return false
             @update_file_search(event)
+            return false
         @container.find(".salvus-project-search-for-file-input-clear").click () =>
             @_file_search_box.val('').focus()
             @update_file_search()


html-edit: insert images from local files (needs to work with proper links)

--- salvus/page/syncdoc.coffee
+++ salvus/page/syncdoc.coffee
@@ -2444,6 +2444,31 @@
             div.unbind()  # remove all event handlers
             delete output_mark.finish_editing

+        localize_image_links = (e) =>
+            # make relative links to images use the raw server
+            for x in e.find("img")
+                y = $(x)
+                src = y.attr('src')
+                if src[0] == '/' or src.indexOf('://') != -1
+                    continue
+                new_src = "/#{@project_id}/raw/#{@file_path()}/#{src}"
+                y.attr('src', new_src)
+
+        unlocalize_image_links = (e) =>
+            prefix = "/#{@project_id}/raw/#{@file_path()}/"
+            prefix2 = document.URL
+            i = 10 + prefix2.slice(10).indexOf('/')
+            prefix2 = prefix2.slice(0,i) + prefix
+            n  = prefix.length
+            n2 = prefix2.length
+            for x in e.find("img")
+                y = $(x)
+                src = y.attr('src')
+                if src.slice(0, n) == prefix
+                    y.attr('src', src.slice(n))
+                else if src.slice(0, n2) == prefix2
+                    y.attr('src', src.slice(n2))
+
         output_mark.finish_editing = finish_editing

         div_changed = () =>
@@ -2458,6 +2483,8 @@
                 delim = t0.data('delim')
                 t.replaceWith(" #{delim}#{t0.find('textarea').val()}#{delim} ")
             e.unmathjax()
+            unlocalize_image_links(e)
+
             new_html = unwrap_nbsp(html_beautify(e.html()))
             if new_html == last_html
                 return
@@ -2474,6 +2501,7 @@
             #console.log("from=",from, "to=",to, "s='#{s}'")
             cm.replaceRange(s, {line:from,ch:0}, {line:to-1,ch:v.length})
             @sync()
+            localize_image_links(div)
             ignore_changes = false

         div.make_editable
@@ -2512,9 +2540,11 @@
             if new_html != last_html
                 #console.log("changed in our selection from '#{last_html}' to '#{new_html}'")
                 div.html(new_html)
-                div.mathjax()
+                mathjax()
+                localize_image_links(div)

         cm.on('change', on_cm_change)
+        localize_image_links(div)

         div.focus()


SCREEN 2275 - Handle save token authorization error.

--- src/main.coffee
+++ src/main.coffee
@@ -124,7 +124,7 @@
             find('.fr_loading').
             html(FormRenderer.t.not_supported.replace(/\:url/g, @projectUrl()))
         else if xhr.responseJSON?.error == 'Token expired. Verify identity.'
-          @$el.find('.fr_loading').html(JST["partials/verify"]({
+          @$el.html(JST["partials/verify"]({
             'template': xhr.responseJSON?.template
             'href': xhr.responseJSON?.verify_api_endpoint
           }))
@@ -309,6 +309,11 @@
           serverErrorText: xhr.responseJSON?.error
           serverErrorKey: xhr.responseJSON?.error_key
           submitting: false
+        if xhr.responseJSON?.error == 'Token expired. Verify identity.'
+          @$el.html(JST["partials/verify"]({
+            'template': xhr.responseJSON?.template
+            'href': xhr.responseJSON?.verify_api_endpoint
+          }))

   waitForRequests: (cb) ->
     if @requests > 0


add Support for list all objects

add Support for list all objects

--- src/main.coffee
+++ src/main.coffee
@@ -90,7 +90,10 @@
         console.log "#{@host}:#{@port} receive request..."
         console.log JSON.stringify(req.body)
         if(req.body.type is 'retrive')
-            objstr = @serverObj[req.body.objName]
+            obj = @serverObj
+            if req.body.objName?
+                obj = @serverObj[req.body.objName]
+            objstr = JSON.stringify(obj)
             res.write(objstr)
             res.end()
             return
@@ -106,8 +109,7 @@
             return

     createSkeleton: (endPoint, obj)->
-        objDesc = @serializeObject(obj)
-        @serverObj[endPoint] = JSON.stringify(objDesc)
+        @serverObj[endPoint] = @serializeObject(obj)

     serializeObject : (obj)->
         if obj? and obj.__r_type?


added splats for varargs to array, added unwrapping for arrays passed in

--- lib/chain-builder.coffee
+++ lib/chain-builder.coffee
@@ -11,14 +11,14 @@

 module.exports = builder =                    # export singleton object

-  chain: (array) ->                           # build chain over array
+  chain: (array...) ->                        # build chain over array

     unless array?                             # null/undefined ?
       # TODO: use had to respond with error   # use `had` instead
       throw new Error 'null array param'      # throw error,

-    if typeof array is 'function'             # single argument can be function
-      array = [array]                         # so wrap it in an array
+    if array?[0]?.push?                       # splat causes wrapped array
+      array = array[0]                        # unwrap array

     if array?.length > 0                      # avoid work for empty array

@@ -48,14 +48,14 @@
       return -> true                          # noop fn returns true

-  pipeline: (array) ->                        # build pipeline over array
+  pipeline: (array...) ->                     # build pipeline over array

     unless array?                             # null/undefined ?
       # TODO: use had to respond with error   # use `had` instead
       throw new Error 'null array param'      # throw error,

-    if typeof array is 'function'             # single argument can be function
-      array = [array]                         # so wrap it in an array
+    if array?[0]?.push?                       # splat causes wrapped array
+      array = array[0]                        # unwrap array

     unless array?.length > 0                  # avoid work for empty array
       # TODO: warning. had'd warn


menus: Fix exports

menus: Fix exports

--- src/js/menus.coffee
+++ src/js/menus.coffee
@@ -7,8 +7,6 @@
 Emitter = require('emitter')

 trakttv = require('trakttv')
-
-menus = {}

 ICON_MENU_UNCHECKED = 'images/icon_menu_unchecked.png'
 ICON_MENU_CHECKED = 'images/icon_menu_checked.png'
@@ -289,8 +287,6 @@

     @readyEmitter.notify()

-menus.ToWatch = ToWatch
-
 class Upcoming extends Menu
   constructor: (@TimeFormatAccessor, @userDateFormat = "D MMMM YYYY") ->
     super
@@ -348,8 +344,6 @@

           detailedItemCard.show()

-menus.Upcoming = Upcoming
-
 class MyShows extends Menu
   constructor: () ->
     super
@@ -382,8 +376,6 @@

     updateMenuSections @menu, sections
     @readyEmitter.notify()
-
-menus.MyShows = MyShows

 class Episodes extends Menu
   constructor: (showTitle, episodes) ->
@@ -415,8 +407,6 @@

     @readyEmitter.notify()

-menus.Episodes = Episodes
-
 class Seasons extends Menu
   constructor: (showID, showTitle, seasons) ->
     super
@@ -457,15 +447,14 @@
       )
     @readyEmitter.notify()

-menus.Seasons = Seasons

 class Main extends Menu
   constructor: (TimeFormatAccessor, initSettings, fetchData) ->
     super
-    @toWatchMenu = new menus.ToWatch()
-    @myShowsMenu = new menus.MyShows()
-    @upcomingMenu = new menus.Upcoming TimeFormatAccessor
-    @advancedMenu = new menus.Advanced initSettings, fetchData, TimeFormatAccessor
+    @toWatchMenu = new ToWatch()
+    @myShowsMenu = new MyShows()
+    @upcomingMenu = new Upcoming TimeFormatAccessor
+    @advancedMenu = new Advanced initSettings, fetchData, TimeFormatAccessor

     @menu = createDefaultMenu(
       sections: [
@@ -521,8 +510,6 @@

     @readyEmitter.notify()

-menus.Main = Main
-
 class Advanced extends Menu
   constructor: (@initSettings, @fetchData, @TimeFormatAccessor) ->
     super
@@ -572,6 +559,9 @@
             element.subtitle = "24h"
           @menu.item e.sectionIndex, e.itemIndex, element

-menus.Advanced = Advanced
-
-module.exports = menus
+module.exports =
+  Main: Main
+  ToWatch: ToWatch
+  Upcoming: Upcoming
+  MyShows: MyShows
+  Advanced: Advanced


test for webp support without modernizr

test for webp support without modernizr

--- src/js/tools.coffee
+++ src/js/tools.coffee
@@ -1,15 +1,21 @@
 # webp
 require ['jquery'], ($) ->
-    useWebp = false
-    $('noscript[data-webp]').each ( _, t ) ->
-        $t = $(t)
-        img = window.document.createElement 'img'
-        img.src    = if useWebp then $t.data 'webp' else $t.data 'src'
-        img.width  = $t.data 'width'
-        img.height = $t.data 'height'
-        img.alt    = $t.data 'alt'
-        $t.replaceWith img
+    image = new Image
+    image.onerror = image.onload = (event) ->
+        supportsWebp = event.type is 'load' and image.width is 1

+        window.document.documentElement.className += ' ' + if supportsWebp then 'webp' else 'no-webp'
+
+        $('noscript[data-webp]').each ( _, t ) ->
+            $t = $(t)
+            img = new Image
+            img.src    = if supportsWebp then $t.data 'webp' else $t.data 'src'
+            img.width  = $t.data 'width'
+            img.height = $t.data 'height'
+            img.alt    = $t.data 'alt'
+            $t.replaceWith img
+
+    image.src = 'data:image/webp;base64,UklGRiQAAABXRUJQVlA4IBgAAAAwAQCdASoBAAEAAwA0JaQAA3AA/vuUAAA='

 # remove analytics parameter
 require [], ->