Remove the old defaults/options merge (left by mistake)

--- src/chaplin/dispatcher.coffee
+++ src/chaplin/dispatcher.coffee
@@ -31,8 +31,6 @@
       @settings = _(options).defaults
         controllerPath: 'controllers/'
         controllerSuffix: '_controller'
-
-      @settings = _.extend(defaults, options)

       # Listen to global events
       @subscribeEvent 'matchRoute', @matchRoute


Remove undocumented silent option for !router:route*.

--- src/chaplin/lib/router.coffee
+++ src/chaplin/lib/router.coffee
@@ -92,17 +92,14 @@
         handler.callback path, options
         return true

-    if options.silent
-      false
-    else
-      throw new Error 'Router#route: request was not routed'
+    throw new Error 'Router#route: request was not routed'

   # Find the URL for given criteria using the registered routes and
   # provided parameters. The criteria may be just the name of a route
   # or an object containing the name, controller, and/or action.
   # Warning: this is usually **hot** code in terms of performance.
   # Returns the URL string or false.
-  reverse: (criteria, params, silent) ->
+  reverse: (criteria, params) ->
     root = @options.root

     if params? and typeof params isnt 'object'
@@ -121,10 +118,7 @@
         return url

     # We didn't get anything.
-    if silent
-      false
-    else
-      throw new Error 'Router#reverse: invalid route specified'
+    throw new Error 'Router#reverse: invalid route specified'

   # Handler for the global !router:route event.
   routeHandler: (path, options, callback) ->
@@ -147,7 +141,7 @@
       callback = options
       options = {}

-    path = @reverse name, params, options?.silent
+    path = @reverse name, params
     routed = @route path, options
     callback? routed


Added test for removeListener

Added test for removeListener

Signed-off-by: Diwank Singh <a78369447041e6bcf2913d083e4ed301785fc609@gmail.com>

--- test/index.coffee
+++ test/index.coffee
@@ -42,6 +42,18 @@

         events.emit 'wait', 500

+      'should be removed when .removeListener is called': (done) ->
+        events = new Events
+
+        fn = ->* 'Hello'
+        events.on 'hello', fn
+        events.emit 'hello'
+
+        events.removeListener 'hello', fn
+
+        assert.equal false, events.emit 'hello'
+        done null
+
       'registered by .once should only be called once': (done) ->
         events = new Events

@@ -50,3 +62,4 @@

         assert.equal false, events.emit 'hello'
         done null
+


Fastfix

Fastfix

--- tests/game.coffee
+++ tests/game.coffee
@@ -41,7 +41,6 @@
 config = require '../config.js'
 game = require '../lib/game'
 mg = require '../lib/migration'
-tables = require '../lib/tables'
 async = require 'async'
 sync = require 'sync'
 anyDB = require 'any-db'
@@ -491,16 +490,16 @@
 		test.done()

-fixTest = (obj) ->
-	for attr of obj
-		if attr is 'setUp' or attr is 'tearDown'
-			continue
-
-		if obj[attr] instanceof Function
-			obj[attr] = ((origTestFunc, t) -> (test) ->
-					console.log(t)
-					origTestFunc(test)
-				)(obj[attr], attr)
-		else
-			fixTest(obj[attr])
-fixTest exports
+#fixTest = (obj) ->
+#	for attr of obj
+#		if attr is 'setUp' or attr is 'tearDown'
+#			continue
+#
+#		if obj[attr] instanceof Function
+#			obj[attr] = ((origTestFunc, t) -> (test) ->
+#					console.log(t)
+#					origTestFunc(test)
+#				)(obj[attr], attr)
+#		else
+#			fixTest(obj[attr])
+#fixTest exports


Adjust configuration file parse process

Adjust configuration file parse process

--- src/javascripts/direct.coffee
+++ src/javascripts/direct.coffee
@@ -1,4 +1,3 @@
-IframeResizer = require('./iframe_resizer.coffee')
 SubscribeButtonTrigger = require('./subscribe_button_trigger.coffee')
 PodigeePodcastPlayer = require('./app.coffee')
 $ = require('jquery')
@@ -9,25 +8,19 @@

     config = @elem.getAttribute('data-configuration').replace(/(^\s+|\s+$)/g, '')
     @id = @randomId(config)
+    @configuration = @getInSiteConfig(config) || {json_config: config}

-    @configuration = if typeof config == 'string'
-      if config.match(/^{/)
-        JSON.parse(config)
-      else
-        @getInSiteConfig(config) || {json_config: config}
-    else
-      config
     try
       @configuration.customOptions = JSON.parse(@elem.getAttribute('data-options'))
     catch
       console.debug('[Podigee Podcast Player] data-options has invalid JSON')
-
+
     @buildPlayer(html)
     @configuration.id = @player.id
     @setupSubscribeButton()
     new PodigeePodcastPlayer("##{@playerId()}", @configuration, scriptSrc)
     @replaceElem()
-
+
   getInSiteConfig: (config) ->
     inSiteConfig = if !(config.indexOf('http') == 0) && config.match(/\./) && !config.match(/^\//)
       configSplit = config.split('.')
@@ -79,7 +72,7 @@
     @elem.parentNode.replaceChild(@player, @elem)

-class DirectWrapper
+class DirectWrapper
   constructor: () ->
     @setUpPlayers()

@@ -88,7 +81,7 @@
     unless window.location.protocol.match(/^https/)
       @scriptSrc = scriptSrc.replace(/^https/, 'http')
     @scriptSrc = @scriptSrc.match(/(^.*\/)/)[0].replace(/javascripts\/$/, '').replace(/\/$/, '')
-
+

   getDirectHtml: () ->
     rendered = $.Deferred()
@@ -116,7 +109,7 @@
         players = []
         elems = document.querySelectorAll('script.podigee-podcast-player')

-        if elems.length
+        if elems.length
           self.origin(elems[0])
           self.getDirectHtml().done =>
             self.appendCss()


Start looking for data repartition

Start looking for data repartition

--- admin/static/coffee/namespaces/namespace.coffee
+++ admin/static/coffee/namespaces/namespace.coffee
@@ -181,6 +181,12 @@
                 svg = d3.select('.pie_chart-data_in_memory').attr('width', width).attr('height', height).append('svg:g').attr('transform', 'translate('+width/2+', '+height/2+')')
                 arcs = svg.selectAll('path').data(d3.layout.pie().sort(null)(data_pie)).enter().append('svg:path').attr('fill', (d,i) -> color(i)).attr('d', arc)

+
+            #namespaces.models[0].get('computed_shards')
+            #namespaces.models[0].compute_shard_rows_approximation(["", "hgughs49"])
+            #namespaces.models[0].compute_shard_rows_approximation(namespaces.models[0].get('computed_shards').models[0])
+
+
             return @

         update_history_opsec: =>


[modify] remove async option and add timeout option.

--- app/assets/javascripts/cms/lib/edit_lock.coffee
+++ app/assets/javascripts/cms/lib/edit_lock.coffee
@@ -43,7 +43,7 @@
       dataType: "json"
       data:
         _method: "delete"
-      async: false
+      timeout: 5000
     # must return void
     return


FIX event binding on dynamic elements

FIX event binding on dynamic elements

Elements retrieved from dynamic getters can't bind wrapped events.
Actually, this was the callback problem, because element ensuring used
selector definition from view.

If not selector is defined, it now retrieve the element directly from
getter.

Close #2

--- app/assets/javascripts/darwin/controller.coffee
+++ app/assets/javascripts/darwin/controller.coffee
@@ -52,9 +52,12 @@
             if el == 'root'
               sel = 'root'
             else
-              sel = ( @view.selectors[ el ] or @view._find_alternate_name( el ) ).sel
+              sel = ( @view.selectors[ el ] or @view._find_alternate_name( el ) )?.sel

-            $target = $target.parents( sel ).first() unless $target.is( sel )
+              if sel
+                $target = $target.parents( sel ).first() unless $target.is( sel )
+              else
+                $target = @view.get( el )

           args = [ $target, event ]
           args.push arguments[i] for i in [1..(arguments.length - 1)] if arguments.length > 1


Remove commented code from Gquery

Remove commented code from Gquery

--- app/assets/javascripts/lib/models/gquery.coffee
+++ app/assets/javascripts/lib/models/gquery.coffee
@@ -8,8 +8,6 @@

   safe_present_value: =>
     x = @present_value()
-    # if @get('key') == 'chp_heat_and_electricity_from_wet_biomass'
-    #   return (if @is_acceptable_value(x) then -x else 0)
     if @is_acceptable_value(x) then x else 0

   safe_future_value: =>


coffeescript for custom map styles

coffeescript for custom map styles

--- app/assets/javascripts/views/map_view.js.coffee
+++ app/assets/javascripts/views/map_view.js.coffee
@@ -1,6 +1,19 @@
 Wmsb.Views.MapView = Backbone.View.extend
   events:
     'click a.student-name': 'updateCurrentStudent'
+
+  styles: [
+    stylers: [
+      { "saturation": -30 }
+      { "lightness": 31 }
+      { "weight": 0.4 }
+      { "gamma": 0.78 }
+      { "hue": "#3b97d3" }
+    ]
+  ]
+
+  styledMap: ->
+    new google.maps.StyledMapType @styles, name: 'Boston Public Schools'

   initialize: (options) ->
     @mapEl             = document.getElementById 'map-canvas'
@@ -15,8 +28,12 @@
     @map = new google.maps.Map @mapEl, {
       center: @currentAssignment.get('latLng')
       zoom: 14
-      mapTypeId: google.maps.MapTypeId.ROADMAP
+      mapTypeControlOptions:
+        mapTypeIds: [google.maps.MapTypeId.ROADMAP, 'map_style']
     }
+
+    @map.mapTypes.set 'map_style', @styledMap()
+    @map.setMapTypeId 'map_style'

     @updateMarker()


skipping unmatched items in equation

skipping unmatched items in equation

this can happen if the elements are updated and the equation is not

--- vendor/assets/javascripts/token-text-area.coffee
+++ vendor/assets/javascripts/token-text-area.coffee
@@ -176,7 +176,9 @@
   fillFromEquation: (equation, items) ->
     html = equation.replace(/\#(\w+)\#/g, (dirtyId) ->
       id = dirtyId.replace(/\#/g, '')
-      name = items.filter( (item) -> item.id == id )[0].name
+      foundItem = items.filter( (item) -> item.id == id )[0]
+      return '' unless foundItem
+      name = foundItem.name
       return ' ' + name + ' '
     )
     @input.html( html )


fixing title on stats page

fixing title on stats page

--- app/front/js/controllers/page_controller.coffee
+++ app/front/js/controllers/page_controller.coffee
@@ -1,10 +1,14 @@
-Application.controller 'pageController', ['$scope', 'Theme', ($scope, Theme) ->
+Application.controller 'pageController', ['$scope', 'Theme', '$state', ($scope, Theme, $state) ->

   $scope.current_theme = Theme

   $scope.page_title = (theme) ->
-    return 'TmTheme Editor' unless theme
-    "#{theme.json.name} • TmTheme Editor"
+    # TODO: use router custom data to set the title
+    if $state.includes("editor.*")
+      return 'TmTheme Editor' unless theme
+      "#{theme.json.name} • TmTheme Editor"
+    else
+      "TmTheme Editor"

   return
 ]


SocialWorker: pass account instead of accountId

--- workers/social/lib/social/models/apitoken.coffee
+++ workers/social/lib/social/models/apitoken.coffee
@@ -53,34 +53,33 @@

   @create: (data, callback) ->

-    { accountId, group } = data
+    { account, group } = data

-    unless accountId and group
-      return callback new KodingError 'accountId and group must be set!'
+    unless account and group
+      return callback new KodingError 'account and group slug must be set!'

     queue = [

       ->
-        JAccount.one { _id : accountId }, (err, account) ->
-          return callback err                                   if err
-          return callback new KodingError 'account not found!'  unless account
-          queue.next()
+        # validating data params
+        unless account instanceof JAccount
+          return callback new KodingError 'account is not an instance of Jaccount!'

-      ->
         JGroup.one { slug : group }, (err, group_) ->
           return callback err                                 if err
           return callback new KodingError 'group not found!'  unless group_
           queue.next()

       ->
+        # creating token
         token = new JApiToken
           code     : hat()
           group    : group
-          originId : accountId
+          originId : account.getId()

         token.save (err) ->
           return callback err  if err
-          callback err, token
+          callback null, token

     ]

@@ -97,7 +96,7 @@
       unless account and group
         return callback new KodingError 'account and group must be set!'

-      data = { group, accountId : account.getId() }
+      data = { group, account }
       JApiToken.create data, callback


Update platform.coffee

Update platform.coffee

--- src/tasks/build/base/platform.coffee
+++ src/tasks/build/base/platform.coffee
@@ -5,10 +5,10 @@
   helpers = require('../../helpers')(grunt)

   remote = (platform, fn) ->
-    helpers.exec "phonegap remote build #{platform} #{helpers.setVerbosity()}", fn
+    helpers.exec "cordova remote build #{platform} #{helpers.setVerbosity()}", fn

   local = (platform, fn) ->
-    helpers.exec "phonegap build #{platform} #{helpers.setVerbosity()}", fn
+    helpers.exec "cordova build #{platform} #{helpers.setVerbosity()}", fn

   runAfter = (provider, platform, fn) ->
     adapter = path.join __dirname, '..', 'after', provider, "#{platform}.js"


Fix left over links

Fix left over links

--- web/source/javascripts/angular-app.js.coffee
+++ web/source/javascripts/angular-app.js.coffee
@@ -291,7 +291,7 @@
         links = _.filter(allLinks, (l) -> l.distance < (minDist + maxDist)*scope.linkPercentage)
         links = _.filter(links, (l) -> l.source.cluster == l.target.cluster) if scope.disconnectGroups # FIXME

-        link = link.data(links, (d) -> d.key)
+        link = linkGroup.selectAll('line.link').data(links, (d) -> d.key)
         link.exit().remove()
         link.enter().append('line').attr('class', 'link')


label listens to selection events

label listens to selection events

--- src/views/labels/LabelRowView.coffee
+++ src/views/labels/LabelRowView.coffee
@@ -12,6 +12,8 @@
     @listenTo @g.vis,"change:metacell", @drawR
     @listenTo @g.zoomer, "change:rowHeight", ->
       @el.style.height = @g.zoomer.get("rowHeight") + "px"
+
+    @listenTo @g.selcol,"change reset add", @setSelection

   draw: ->
     @removeViews()
@@ -30,4 +32,13 @@

     @el.setAttribute "class", "biojs_msa_labelrow"
     @el.style.height = @g.zoomer.get("rowHeight") * @model.attributes.height + "px"
+
+    @setSelection()
     @
+
+  setSelection: ->
+    sel = @g.selcol.getSelForRow @model.id
+    if sel.length > 0
+      @el.style.fontWeight = "bold"
+    else
+      @el.style.fontWeight = "normal"


missed a build

missed a build

--- js/inner/build.coffee
+++ js/inner/build.coffee
@@ -345,7 +345,7 @@
   trackRetryBuild: (data, clearCache, SSH) =>
     mixpanel.track("Trigger Build",
       "vcs-url": data.vcs_url.substring(19)
-      "build-num": build.build_num
+      "build-num": data.build_num
       "retry?": true
       "clear-cache?": clearCache
       "ssh?": SSH)


wiki links

wiki links

--- packages/tower-generator/server/generators/tower/app/templates/app/templates/shared/layout/_navigation.coffee
+++ packages/tower-generator/server/generators/tower/app/templates/app/templates/shared/layout/_navigation.coffee
@@ -8,4 +8,4 @@
         b class: 'caret'
       ul class: 'dropdown-menu', ->
         li ->
-          linkTo 'Tower.js', 'https://github.com/viatropos/tower-docs'
+          linkTo 'Tower.js', 'https://github.com/viatropos/tower/wiki'


Use ||= to not ignore some clickable elements, no negative tabindex

Elements with `tabindex="n"` for parseInt(n) < 0 cannot be selected by
pressing the tab key, according to the spec. If we have no other reason
to suspect that the element is clickable, we may as well ignore them.

--- content_scripts/link_hints.coffee
+++ content_scripts/link_hints.coffee
@@ -176,18 +176,19 @@
         when "a"
           isClickable = true
         when "textarea"
-          isClickable = not element.disabled and not element.readOnly
+          isClickable ||= not element.disabled and not element.readOnly
         when "input"
-          unless element.getAttribute("type")?.toLowerCase() == "hidden" or
-                 element.disabled or
-                 (element.readOnly and DomUtils.isSelectable element)
-            isClickable = true
+          isClickable ||= not (element.getAttribute("type")?.toLowerCase() == "hidden" or
+                               element.disabled or
+                               (element.readOnly and DomUtils.isSelectable element))
         when "button", "select"
-          isClickable = true unless element.disabled
+          isClickable ||= not element.disabled

       # Elements with tabindex are sometimes useful, but usually not. We can treat them as second class
       # citizens when it improves UX, so take special note of them.
-      if element.hasAttribute("tabindex") and not isClickable
+      tabIndexValue = element.getAttribute("tabindex")
+      tabIndex = if tabIndexValue == "" then 0 else parseInt tabIndexValue
+      unless isClickable or isNaN(tabIndex) or tabIndex < 0
         isClickable = onlyHasTabIndex = true

       continue unless isClickable # If the element isn't clickable, do nothing.


check for instanceTypes existency to show machine specs

--- client/home/lib/virtualmachines/components/machineslist/machinedetails.coffee
+++ client/home/lib/virtualmachines/components/machineslist/machinedetails.coffee
@@ -307,16 +307,16 @@

   configs = globals.config.providers
   { instanceTypes } = configs[providerName]
-
-  instanceType = jMachine.meta?.instance_type ? instanceTypes['base-vm']
+  if instanceTypes
+    instanceType = jMachine.meta?.instance_type ? instanceTypes['base-vm']
+
+    instanceData = instanceTypes[instanceType]
+    { ram, cpu } = instanceData ? instanceTypes[instanceTypes['base-vm']]
+
   size = jMachine.meta?.storage_size
-
-  instanceData = instanceTypes[instanceType]
-  { ram, cpu } = instanceData ? instanceTypes[instanceTypes['base-vm']]
-
-  disk = if size? then "#{size}GB HDD" else 'N/A'
-
-  return [providerName, instanceType, ram, cpu, disk]
+  disk = if size then "#{size}GB HDD" else null
+
+  return [providerName, instanceType, ram, cpu, disk].filter(Boolean)

 EditVMNameDescription = ->


payment: fix the text of always on vms for first 2 plan

--- client/Pricing/views/plans.coffee
+++ client/Pricing/views/plans.coffee
@@ -35,7 +35,7 @@
       { partial: '1 Core'       , cssClass: 'cpu' }
       { partial: '3 GB Storage' , cssClass: 'storage' }
       { partial: '3 VMs'        , cssClass: 'vm-count' }
-      { partial: 'Always on'    , cssClass: 'always-on disabled' }
+      { partial: '0 Always on'  , cssClass: 'always-on disabled' }
     ]
   ,
     title        : 'Hobbyist'
@@ -50,7 +50,7 @@
       { partial: '1 Core'       , cssClass: 'cpu' }
       { partial: '3 GB Storage' , cssClass: 'storage' }
       { partial: '3 VMs'        , cssClass: 'vm-count' }
-      { partial: 'Always on'    , cssClass: 'always-on disabled' }
+      { partial: '1 Always on'  , cssClass: 'always-on disabled' }
     ]
   ,
     title        : 'Developer'


recode multi select listeners and events to inline w/ function def

--- client/common/multi-select.coffee
+++ client/common/multi-select.coffee
@@ -4,13 +4,6 @@
   tagName: 'div'
   templateName: 'multi-select-option'

-  init: ->
-    @labelPathDidChange()
-    @valuePathDidChange()
-
-    @_super()
-
-  # todo: make checked?
   selected: Ember.computed(->
     content = @get('content')
     selection = @get('parentView.selection')
@@ -18,24 +11,23 @@
   # if didn't add isFulfilled, true values were never shown
   ).property('content', 'parentView.selection', 'parentView.selection.isFulfilled')

-  labelPathDidChange: Ember.observer('parentView.optionLabelPath', ->
+  labelPathDidChange: (->
     labelPath = Ember.get(this, 'parentView.optionLabelPath') # todo: try other syntax
     return if not labelPath

     Ember.defineProperty(@, 'label', Ember.computed(->
-      @get(labelPath) # bound to caller or not?
+      @get(labelPath)
     ).property(labelPath))
-  )
+  ).on('init').observes 'parentView.optionLabelPath'

-  # todo try other syntax
-  valuePathDidChange: Ember.observer('parentView.optionValuePath', ->
+  valuePathDidChange: (->
     valuePath = @get('parentView.optionValuePath')
     return if not valuePath

     Ember.defineProperty(@, 'value', Ember.computed(->
       @get(valuePath)
     ).property(valuePath))
-  )
+  ).on('init').observes 'parentView.optionValuePath'

 App.MultiSelect = Ember.View.extend
@@ -50,23 +42,17 @@

   optionView: App.MultiSelectOption

-  init: ->
-    @_super()
-    @on('didInsertElement', @, @_triggerChange) # todo, try inline listener
-    # todo: change was for select. Now, a list of checkboxes
-    @on('change', @, @_change)
-
-  selectionDidChange: Ember.observer('selection.@each', ->
-    selection = @get('selection') # todo other syntax
+  selectionDidChange: (->
+    selection = @get('selection')
     if not Ember.isArray(selection)
-      Ember.set(@, 'selection', Ember.A([selection]))
+      @set('selection', Ember.A([selection]))
       return
     @_selectionDidChangeMultiple()
-  )
+  ).observes 'selection.@each'

   # todo: consider consolidating all private "multiple" fns that are needed
   _selectionDidChangeMultiple: ->
-    content = @get('content') # todo other syntax
+    content = @get('content')
     selection = @get('selection')
     selectedIndexes = if content then Ember.EnumerableUtils.indexesOf(content, selection) else [-1]
     options = @$('input[type=checkbox]')
@@ -77,15 +63,17 @@
         adjusted = if @index > -1 then @index else -1  # todo: better name (this is for if prompt offset)
         @selected = Ember.EnumerableUtils.indexOf(selectedIndexes, adjusted) > -1

-  _triggerChange: ->
-    selection = @get('selection') # todo try other syntax
+  _triggerChange: (->
+    selection = @get('selection')
     @selectionDidChange() if not Ember.isNone(selection)

     @_change()
+  ).on('didInsertElement')

-  _change: ->
+  _change: (->
     # todo: get rid of this proxy fn
     @_changeMultiple()
+  ).on('change')

   _changeMultiple: ->
     options = this.$('input[type=checkbox]:checked')
@@ -101,4 +89,4 @@
       if Ember.isArray selection
         Ember.EnumerableUtils.replace selection, 0, Ember.get(selection, 'length'), newSelection
       else
-        Ember.set(@, 'selection', newSelection)
+        @set('selection', newSelection)


only include launch approved projects by default

and always use the cards route for pages, even when admin.

--- app/pages/projects.cjsx
+++ app/pages/projects.cjsx
@@ -20,9 +20,10 @@

   listProjects: ->
     query = {include: 'avatar'}
+    query.cards = true

     if !apiClient.params.admin
-      query.cards = true
+      query.launch_approved = true

     apiClient.type('projects').get Object.assign {}, query, @props.location.query


Fix email unsubscribe token

Fix email unsubscribe token

--- client/shared/helpers/boot.coffee
+++ client/shared/helpers/boot.coffee
@@ -2,9 +2,10 @@

 module.exports =
   bootDat: (callback) ->
+    token  = new URLSearchParams(location.search).get('unsubscribe_token')
     client = new RestfulClient('boot')
     client.get('site').then (siteResponse) ->
-      client.get('user').then (userResponse) ->
+      client.get('user', unsubscribe_token: token).then (userResponse) ->
         if _.all _.pluck([siteResponse, userResponse], 'ok')
           siteResponse.json().then (site) ->
             userResponse.json().then (user) ->


Special-case trailing html entities in url

--- assets/javascripts/helpers/formatting.js.coffee
+++ assets/javascripts/helpers/formatting.js.coffee
@@ -39,6 +39,20 @@
   htmlEscapeText: (text) ->
     return unless text
     text.replace /[&"'><]/g, (character) -> TentStatus.Helpers.HTML_ENTITIES[character]
+
+  htmlUnescapeText: (text) ->
+    for char, entities of HTML_ENTITIES
+      text = text.replace(entities, char)
+    text
+
+  extractTrailingHtmlEntitiesFromText: (text) ->
+    trailing_text = ""
+    for char, entities of TentStatus.Helpers.HTML_ENTITIES
+      regex = new RegExp("(#{TentStatus.Helpers.escapeRegExChars(entities)}?)$")
+      if regex.test(text)
+        trailing_text = text.match(regex)[1] + trailing_text
+        text = text.replace(regex, "")
+    [text, trailing_text]

   flattenUrlsWithIndices: (items) ->
     _flattened = []
@@ -102,11 +116,14 @@
       original = text.substring(item.start_index, item.end_index)
       link_attributes = []

+      [item.url, trailing_text] = TentStatus.Helpers.extractTrailingHtmlEntitiesFromText(item.url)
+      [original, original_trailing_text] = TentStatus.Helpers.extractTrailingHtmlEntitiesFromText(original)
+
       if TentStatus.Helpers.isURLExternal(item.url)
         link_attributes.push "data-view='ExternalLink'"

       html = "" +
-             TentStatus.Helpers.truncate(TentStatus.Helpers.formatUrlWithPath(original), TentStatus.config.URL_TRIM_LENGTH) + ""
+             TentStatus.Helpers.truncate(TentStatus.Helpers.formatUrlWithPath(original), TentStatus.config.URL_TRIM_LENGTH) + "#{trailing_text}"
       delta = html.length - original.length
       updateIndicesWithOffset(urls_and_mentions, item.start_index, delta)
       urls_and_mentions = removeOverlappingItems(urls_and_mentions, item.start_index, item.end_index + delta)


update

update

--- options.coffee
+++ options.coffee
@@ -337,6 +337,7 @@
       ### Other stuff: ###
       # '‰':          '{\\mktsFontfileOptima‰}'
       '·':                                cmd: 'mktsFontfileEbgaramondtwelveregular' # U+00B7 MIDDLE DOT
+      # '↕':      raise: -0.2,   cmd: 'cnxJzr',      glyph: ''
       ### Ideographic description characters: ###
       '↻':      raise: -0.2,   cmd: 'cnxJzr',      glyph: ''
       '↔':      raise: -0.2,   cmd: 'cnxJzr',      glyph: ''


log construction time

log construction time

--- oriDomi.coffee
+++ oriDomi.coffee
@@ -69,6 +69,7 @@
 class root.OriDomi

   constructor: (@el, @settings = {}) ->
+    console.time 'oridomiConstruction'
     if !(@ instanceof OriDomi)
       return new oriDomi @el, @settings

@@ -263,6 +264,7 @@
       @el.appendChild @stages[axis]

     @_callback @settings
+    console.timeEnd 'oridomiConstruction'

   _callback: (options) ->


scrapped extendObj

scrapped extendObj

--- oridomi.coffee
+++ oridomi.coffee
@@ -69,20 +69,6 @@

 showEl = (el) ->
   el.style[css.transform] = 'translate3d(0, 0, 0)'
-
-
-# This function is used to extend option object literals with a set of defaults.
-# It is simple and one dimensional.
-extendObj = (target, source) ->
-  return {} if !target and !source
-  # Check if the extension object is an object literal by casting it and comparing it.
-  return target if source isnt Object source
-  # If the target isn't an object, set it to an empty object literal.
-  target = {} if target isnt Object target
-  # Loop through the extension object and copy its values to the target if they don't exist.
-  (target[prop] = source[prop] unless target[prop]?) for prop of source
-  # Return the extended target object.
-  target

 prep = (fn) ->


Make init async

Make init async

--- picksel.coffee
+++ picksel.coffee
@@ -15,14 +15,19 @@
 md5File = require 'md5-file'
 filedel = require 'filedel'
 fileMove = require 'file-move'
-readline = require 'readline-sync'
-i18n = require("i18n");
+i18n = require 'i18n'
+#pixabay = require 'pixabayjs'
+yn = require 'yn'
+mkdirp = require 'mkdir-p'
+
+readline = require 'readline'
+

 locales = [
   'en'
   'eo'
 ]
-i18nconfig =
+i18nconfig =
   locales: locales
   directory: './locales'
 i18n.configure i18nconfig
@@ -64,12 +69,25 @@
 ]

+class ProjectConfiguration
+  constructor: () ->
+    @directory = ''
+    @images = []
+
 # Redacts the user's API key from the given URL.
 #
 # @param [String] url the URL to redact the API key from
 #
 redactApiKey = (url) -> url.replace user.apiKey, '********'

+ask = (question, callback) ->
+  readlineOptions =
+    input: process.stdin
+    output: process.stdout
+  rl = readline.createInterface readlineOptions
+  rl.question question, (answer) ->
+    rl.close()
+    callback answer

 # Builds a URL for downloading image information.
 #
@@ -82,7 +100,7 @@
     + (if res > 1 then '&response_group=high_resolution' else '') \
     + "&id=#{id}"

-
+
 # Checks whether or not a human-readable resolution name is valid.
 #
 # @param [Number] res the resolution code to check.
@@ -106,18 +124,20 @@
   options =
     spaces: 4
   jsonfile.writeFileSync filename, obj, options
-
-
+
+
 # Persists the currently loaded configuration settings
 #
-persistConfig = () -> persist config, CONFIG_PATH
+persistProject = (obj) ->
+  if obj then config = obj # Replace loaded config.
+  persist config, CONFIG_PATH

 # Persists the currently loaded user settings.
 #
 persistUser = () -> persist user, USER_PATH

-
+
 # Returns true if a request to the Pixabay API was successful.
 #
 # @param [Object] error any error returned with the response
@@ -129,8 +149,8 @@
   && body.hits \
   && body.hits instanceof Array \
   && body.hits.length > 0
-
-
+
+
 # Downloads an image.
 #
 # @param [String] id the Pixabay image ID
@@ -138,43 +158,43 @@
 # @param [String] destination the path of the destination file
 #
 download = (id, resolution, destination) ->
-
+
   # Build URL for API request.
   url = buildUrl(user.apiKey, id, resolution)
-
+
   log.info i18n.__('Requesting information for image from %s', \
                    redactApiKey(url))
-
+
   # Call out to Pixabay for JSON.
   options =
     url: url
     json: true
   request options, (error, response, body) ->
-
+
     # On success.
     if isSuccessful error, body
-
+
       # Get URL of image at correct resolution.
       url = body.hits[0][resolutions[resolution]]
-
+
       log.info i18n.__('Downloading image file from %s', url)
-
+
       # Request image from Pixabay.
       tempDestination = destination + '.pickseltemp'
       file = fs.createWriteStream tempDestination
       req = https.get url, (response) ->
-
+
         # Write to temporary file.
         stream = response.pipe file
         stream.on 'finish', () ->
-
+
           # Does file currently exist at destination?
           exists = existsFile.sync(destination)
-
+
           # Hash temp file (and existing file if possible).
           existingHash = if exists then md5File.sync(destination) else ''
           tempHash = md5File.sync(tempDestination)
-
+
           # If file currently exists and hashes don't match, don't overwrite.
           if exists && existingHash != tempHash
             log.warning "Looks like '#{destination}' has been modified (MD5" \
@@ -188,8 +208,8 @@
     else
       # Download failed.
       log.error "Couldn't get information about image with ID '#{id}'"
-
-
+
+
 # Installs a single image.
 #
 # @param [Object] image the image to grab
@@ -199,7 +219,7 @@
     + "#{humanResolutions[image.resolution]} to #{path}"
   download image.id, image.resolution, path

-
+
 # Installs all images present in the currently loaded configuration.
 #
 install = () -> grab image for image in config.images
@@ -211,27 +231,27 @@
 #
 add = (args) ->
   id = args[3]
-
+
   # Check resolution.
   res = args[4]
   if !isValidResolution res
     log.error "That resolution '#{res}' isn't valid."
     return false
-
+
   # Add new image to configuration file.
   image =
     id: id
     resolution: getResolutionCode res
     destination: args[5]
   config.images.push image
-
-  persistConfig() # Update configuration file.
-
+
+  persistProject() # Update configuration file.
+
   log.info "Added image with ID '#{image.id}' as asset."
-
+
   install() # Freshly install all images.
-
-
+
+
 # Removes an image from installed assets.
 #
 # @param [Object] args the arguments to the program
@@ -245,9 +265,9 @@
       + ' installed in the first place.'
   else
     config.images = filteredImages
-    persistConfig()
-
-
+    persistProject()
+
+
 # Attempts to load the user and dependency configuration files.
 #
 loadConfig = () ->
@@ -265,65 +285,86 @@
       + ' up.'
     return false
   true
-
-
-# Walks the user through initializing their dependency file.
+
+
+# Prompts the user to create a directory if it doesn't exist yet.
+#
+# @param [String] dir the path of the directory
+# @param [Function] callback a callback function
+#
+promptDirectoryCreation = (dir, callback) ->
+  existsFile dir, (err, exists) -> # Does folder exist already?
+    if err || exists
+      callback err, exists # Folder exists already, or error.
+    else
+      ask "Doesn't look like the #{dir} directory exists, create it?", \
+        (answer) ->
+          if yn answer # Create directory? Yes or no.
+            mkdirp dir, (err) ->
+              if err
+                log.error 'Couldn\'t create directory!'
+                callback err, false # Folder doesn't exist and wasn't created.
+              else
+                log.info "Directory created!"
+                callback err, true # Folder didn't exist but was created.
+          else
+            log.info "Okay, the directory won't be created."
+            callback null, false # User opted out of creation.
+
+
+# Walks the user through initializing their project file.
 #
 init = () ->
-
-  # Check we're not going to overwrite an existing project file.
-  if existsFile.sync CONFIG_PATH
-    log.warning 'Looks like this project has already been initialized for' \
-      + ' Picksel.'
-      return false
-
-  newConfig =
-    directory: ''
-    images: []
-
-  console.log 'Let\'s initialize Picksel for this project...'
-
-  directory = readline.question 'Relative to the current directory, where' \
-    + ' would you like to store assets? '
-  newConfig.directory = directory
-
-  # Persist config file.
-  config = newConfig
-  persistConfig()
-
-  log.info "New file created at '#{CONFIG_PATH}' for holding your" \
-    + ' asset dependencies. Feel free to check this file in to source control.'
-
-
+  existsFile CONFIG_PATH, (err, stats) -> # Check for an existing project file.
+    if stats
+      log.warning 'Looks like this project has already been initialized for' \
+        + ' Picksel.' # If file exists, inform user and abort.
+    else
+      log.info 'Let\'s initialize Picksel for this project...'
+      newProj = new ProjectConfiguration() # Initialize new project.
+      ask 'Relative to the current directory, where would you like to store' \
+        + ' assets? ', (answer) ->
+          promptDirectoryCreation answer, (err, exists) ->
+            if !exists # Warn user about missing assets directory.
+              log.warning "The #{answer} directory isn't present on-disk. You" \
+                + " won't be able to install assets until it is."
+            newProj.directory = answer # Add asset path to project.
+            persistProject newProj # Persist new project file.
+            log.info "New file created at '#{CONFIG_PATH}' for holding your" \
+              + ' asset dependencies. Feel free to check this file in to' \
+              + ' source control.' # Project setup success!
+
+
 # Walks the user through initializing their user file.
 #
 auth = () ->
-
+
   # Check we're not going to overwrite an existing user file.
   if existsFile.sync USER_PATH
     log.warning 'Looks like authentication is already set up for this project.'
     return false
-
+
   newUser =
     apiKey: ''
-
+
   console.log 'Let\'s associate a Pixabay account with your local copy of' \
     + ' this project...'
-
+
   apiKey = readline.question 'What\'s your Pixabay API key? To find it you' \
     + ' can log in to the Pixabay website and visit: ' \
     + 'https://pixabay.com/api/docs/ '
+  # TODO: Validate API key?
   newUser.apiKey = apiKey
-
+
   # Persist user file.
   user = newUser
   persistUser()
-
+
   log.info "New file created at '#{USER_PATH}' containing your API key." \
     + " DON'T CHECK THIS FILE IN TO SOURCE CONTROL BECAUSE IT HAS YOUR" \
     + " SECRET API KEY IN IT."
-
-
+
+
 # Prints usage information for the application.
 #
 help = () ->
@@ -341,8 +382,8 @@
     + '    dest The file path to install the image to\n' \
     + '  remove <id>           Removes a dependency on an asset\n' \
     + '    id   The (hash) ID of the image to remove from dependencies'
-
-
+
+
 # Interpret commands.
 switch process.argv[2]
   when 'help' then help()
@@ -354,4 +395,3 @@
         when 'install' then install()
         when 'add' then add process.argv
         when 'remove' then remove process.argv
-


Fix broken persistence functions

Fix broken persistence functions

--- picksel.coffee
+++ picksel.coffee
@@ -97,12 +97,12 @@

 # Persists the currently loaded configuration settings
 #
-persistConfig = () -> persist configPath, config
+persistConfig = () -> persist config, configPath

 # Persists the currently loaded user settings.
 #
-persistUser = () -> persist userPath, user
+persistUser = () -> persist user, userPath

 # Returns true if a request to the Pixabay API was successful.


Uncomment enqueueing recalcs from publisher

--- publish.coffee
+++ publish.coffee
@@ -284,29 +284,25 @@

                                 break

-                    # if needsRecalc
-                    #     if reactivesObj[reactiveName] is undefined
-                    #         # We know this value is dirty, but publish it anyway.
-                    #         reactivesObj[reactiveName] =
-                    #             val: reactiveValue
-                    #             ts: reactiveTs
-                    #             expire: reactiveExpire
-
-                    #     # Else keep publishing the previous value of reactivesObj[reactiveName]
-                    #     # until the recalc task gets popped off the queue asynchronously.
-
-                    #     console.log "Publisher enqueuing <#{modelName} #{JSON.stringify id}>.#{reactiveName}"
-                    #     J.denorm._enqueueReactiveCalc modelName, id, reactiveName
-
-                    # else
-                    #     reactivesObj[reactiveName] =
-                    #         val: reactiveValue
-                    #         ts: reactiveTs
-                    #         expire: reactiveExpire
-                    reactivesObj[reactiveName] =
-                        val: reactiveValue
-                        ts: reactiveTs
-                        expire: reactiveExpire
+                    if needsRecalc
+                        if reactivesObj[reactiveName] is undefined
+                            # We know this value is dirty, but publish it anyway.
+                            reactivesObj[reactiveName] =
+                                val: reactiveValue
+                                ts: reactiveTs
+                                expire: reactiveExpire
+
+                        # Else keep publishing the previous value of reactivesObj[reactiveName]
+                        # until the recalc task gets popped off the queue asynchronously.
+
+                        console.log "Publisher enqueuing <#{modelName} #{JSON.stringify id}>.#{reactiveName}"
+                        J.denorm._enqueueReactiveCalc modelName, id, reactiveName
+
+                    else
+                        reactivesObj[reactiveName] =
+                            val: reactiveValue
+                            ts: reactiveTs
+                            expire: reactiveExpire

         else
             fieldsByModelIdQuery[modelName][id][fieldName] ?= {}


OX-603 Hide unused Gui settings, restructure Gui mode switch


fix more tests

fix more tests

--- lib/assets/javascripts/turbograft/remote.coffee
+++ lib/assets/javascripts/turbograft/remote.coffee
@@ -72,14 +72,16 @@
     formData = ""
     @_iterateOverFormInputs form, (input) =>
       formData = @formAppend(formData, input.name, input.value)
+    formData

   nativeEncodeForm: (form) ->
     formData = new FormData
     @_iterateOverFormInputs form, (input) =>
       if input.type == 'file'
-        # ???
+        formData.append(input.name, input.files[0]) # <- just a guess
       else
         formData.append(input.name, input.value)
+    formData

   _iterateOverFormInputs: (form, callback) ->
     inputs = form.querySelectorAll("input:not([type='reset']):not([type='button']):not([type='submit']):not([type='image']), select, textarea")


Remove double quote from end of array snippet

--- services/web/public/coffee/auto-complete/Snippets.coffee
+++ services/web/public/coffee/auto-complete/Snippets.coffee
@@ -26,7 +26,7 @@
 			\\begin{array}{${1:cc}}
 			$2 & $3 \\\\\\\\
 			$4 & $5
-			\\end{array}"
+			\\end{array}
 		"""
 		meta: "env"
 	}, {


Reinit ckeditors after sorting

Reinit ckeditors after sorting

--- app/assets/javascripts/admin/sortable.coffee
+++ app/assets/javascripts/admin/sortable.coffee
@@ -22,9 +22,21 @@
     $(el).closest(selector).find("li:visible").each (i)->
       $(@).find(targetSelector).val(i+1)

+  reinitCkeditors = (el) ->
+    editors = el.find("div.cke")
+    editors.each (i, e) ->
+      input = $(e).parent().find("textarea")
+      instance = CKEDITOR.instances[input.attr("id")]
+      # ckeditor doesn't tidy up after itself
+      CKEDITOR.remove instance
+      CKEDITOR.replace input.attr("id")
+      $(e).remove()
+
   sort
     .sortable
-      stop: (event, ui) -> updateElements(ui.item)
+      stop: (event, ui) ->
+        updateElements(ui.item)
+        reinitCkeditors(ui.item)
       handle: handleSelector
     .on "fields_added.nested_form_fields", (event, param) ->
       updateElements(event.target)


JQuery objects always exist - bug

JQuery objects always exist - bug

--- app/assets/javascripts/application.js.coffee
+++ app/assets/javascripts/application.js.coffee
@@ -51,7 +51,7 @@
 #//--------------------------------------//

 #Load the initial page in non-embedded layout
-if $('#opt_discovery')?
+if $('#opt_discovery'),length
   #Pass in the option as a url param (Digital Cameras are default)
   window.opt_category_id = decodeURI((RegExp('([?]|&)[Cc]ategory_id=(.+?)(&|$)').exec(location.search)||[0,0,22474])[2])
   if (location.hash)


hardcode domain in bookmarklet

hardcode domain in bookmarklet

--- app/assets/javascripts/bookmarklet.js.coffee
+++ app/assets/javascripts/bookmarklet.js.coffee
@@ -1,4 +1,4 @@
-url = "http://" + window.location.host + "/bookmarklet?"
+url = "http://nearness.org/bookmarklet?"

 params = {}
 if NN_BOOKMARKLET? && NN_BOOKMARKLET.preset?


Add test to pass through the message if min is not set

--- test/pass-through-if-length-greater-than-min-spec.coffee
+++ test/pass-through-if-length-greater-than-min-spec.coffee
@@ -44,3 +44,12 @@
             min: 2

         expect(@sut.onEnvelope envelope).not.to.exist
+
+    describe 'when a min isnt set', ->
+      it 'should return the message', ->
+        envelope =
+          message: [1,2,3]
+          config:
+            min: undefined
+
+        expect(@sut.onEnvelope envelope).to.deep.equal [1,2,3]


fixed email logo link

fixed email logo link

--- services/web/app/coffee/Features/Email/Layouts/NotificationEmailLayout.coffee
+++ services/web/app/coffee/Features/Email/Layouts/NotificationEmailLayout.coffee
@@ -314,7 +314,7 @@

-
+


Update code to work with linter

Update code to work with linter

--- src/backbone.typeahead.coffee
+++ src/backbone.typeahead.coffee
@@ -109,7 +109,8 @@
     lists = []
     shortestList = null
     firstChars = _(queryTokens).chain().map((t) -> t.charAt(0)).uniq().value()
-    checkIfShortestList = (list) => shortestList = list if list.length <= (shortestList?.length or @length)
+    checkIfShortestList = (list) =>
+      shortestList = list if list.length <= (shortestList?.length or @length)

     _.all firstChars, (firstChar) =>
       list = @_adjacency[firstChar]
@@ -177,7 +178,8 @@
         add = true
         @_removeFromIndex id: model.previous(model.idAttribute)
       else if event.indexOf('change:') is 0
-        if not @typeaheadAttributes? or _.indexOf(_.map(@typeaheadAttributes, (att) -> 'change:' + att), event) >= 0
+        changeEventList = _.map(@typeaheadAttributes, (att) -> 'change:' + att)
+        if not @typeaheadAttributes? or _.contains(changeEventList, event)
           add = true
           @_removeFromIndex model


fixup! Do not fit bounds if there is only one point to fit

--- src/coffee/controllers.coffee
+++ src/coffee/controllers.coffee
@@ -380,7 +380,7 @@
         if not markersToFit?
             markersToFit = _.values placeMarkers
             markersToFit.push findMeMarker
-            markersToFit = _.filter markersToFit, (m) -> m.getVisible()
+            markersToFit = _.filter markersToFit, (m) -> m?.getVisible()
         if markersToFit.length > 1
             bounds = markersToFit.reduce (b, m) ->
                 b.extend m.getPosition()


:fire: custom-event-mixin.coffee

:fire: custom-event-mixin.coffee

--- src/custom-event-mixin.coffee
+++ src/custom-event-mixin.coffee
@@ -1,17 +0,0 @@
-module.exports =
-CustomEventMixin =
-  componentWillMount: ->
-    @customEventListeners = {}
-
-  componentWillUnmount: ->
-    for name, listeners in @customEventListeners
-      for listener in listeners
-        @getDOMNode().removeEventListener(name, listener)
-    return
-
-  addCustomEventListeners: (customEventListeners) ->
-    for name, listener of customEventListeners
-      @customEventListeners[name] ?= []
-      @customEventListeners[name].push(listener)
-      @getDOMNode().addEventListener(name, listener)
-    return


check baddies too

check baddies too

--- test/files/verify_detached_sigs.iced
+++ test/files/verify_detached_sigs.iced
@@ -3,10 +3,24 @@
 {do_message} = require '../../lib/openpgp/processor'
 {keys,data}  = require '../data/detached'
 {WordArray}  = require 'triplesec'
+{MRF}        = require '../../lib/rand'

 #=================================================

 ring = new PgpKeyRing()
+
+#==========================================
+
+random = (hi) -> MRF().random_word() % hi
+
+#==========================================
+
+corrupt = (inbuf, cb) ->
+  outbuf = new Buffer inbuf
+  i = random inbuf.length
+  c = (random 0xff) + 1
+  outbuf.writeUInt8((inbuf.readUInt8(i) ^ c), i)
+  cb outbuf

 #==========================================

@@ -19,6 +33,7 @@
   # Base-64-decode the file data
   for key,val of data
     val.data = new Buffer val.data, 'base64'
+    await corrupt val.data, defer val.bad_data

   cb()

@@ -39,11 +54,18 @@

 #==========================================

-verify_good_sig_all_at_once = (T, name, {data,sig}, cb) ->
-  data_fn = make_data_fn(data) # ignore for now...
+good_check_sig_all_at_once = (T, name, {data,sig,bad_data}, cb) ->
   await do_message { keyfetch : ring, armored : sig, data }, defer err
   T.no_error err, "sig worked for #{name}"
-  T.waypoint "Sig #{name} checked out"
+  T.waypoint "Sig #{name} / good checked out"
+  cb()
+
+#==========================================
+
+bad_check_sig_all_at_once = (T, name, {sig,bad_data}, cb) ->
+  await do_message { keyfetch : ring, armored : sig, data : bad_data }, defer err
+  T.assert err?, "errored out on bad signature"
+  T.waypoint "Sig #{name} / bad checked out"
   cb()

 #==========================================
@@ -59,7 +81,14 @@

 exports.verify_good_sigs_all_at_once = (T,cb) ->
   for key, val of data
-    await verify_good_sig_all_at_once T, key, val, defer()
+    await good_check_sig_all_at_once T, key, val, defer()
+  cb()
+
+#==========================================
+
+exports.nix_bad_sigs_all_at_once = (T,cb) ->
+  for key, val of data
+    await bad_check_sig_all_at_once T, key, val, defer()
   cb()

 #==========================================


Configure npm to use mock registry

Configure npm to use mock registry

--- spec/clean-spec.coffee
+++ spec/clean-spec.coffee
@@ -35,6 +35,7 @@
       process.env.ATOM_HOME = atomHome
       process.env.ATOM_ELECTRON_URL = "http://localhost:3000/node"
       process.env.ATOM_ELECTRON_VERSION = 'v0.10.3'
+      process.env.npm_config_registry = 'http://localhost:3000/'

       moduleDirectory = path.join(temp.mkdirSync('apm-test-module-'), 'test-module-with-dependencies')
       wrench.copyDirSyncRecursive(path.join(__dirname, 'fixtures', 'test-module-with-dependencies'), moduleDirectory)


Remove use of Function::bind in specs

Remove use of Function::bind in specs

--- spec/specs/once.coffee
+++ spec/specs/once.coffee
@@ -14,7 +14,7 @@
   it "Responds synchronously", ->
     values = []
     s = Bacon.once(1)
-    s.onValue(values.push.bind(values))
+    s.onValue((value) => values.push(value))
     expect(values).to.deep.equal([1])
-    s.onValue(values.push.bind(values))
+    s.onValue((value) => values.push(value))
     expect(values).to.deep.equal([1])


Add test case to our Utils.chunk function and refactor tests

--- spec/utils-spec.coffee
+++ spec/utils-spec.coffee
@@ -88,10 +88,17 @@
     it 'returns an array of arrays of specified length', ->
       expect(Utils.chunk([1, 2, 3, 4], 2)).toEqual [[1, 2], [3, 4]]

-    context 'when array is not an array', ->
-      it 'returns an empty array', ->
-        expect(Utils.chunk(null, 2)).toEqual []
+    it 'handles counts that do not divide evenly into the array length', ->
+      expect(Utils.chunk([1, 2, 3, 4, 5], 2)).toEqual [[1, 2], [3, 4], [5]]

-    context 'when count is null or 0', ->
-      it 'returns empty', ->
-        expect(Utils.chunk([1, 2], null)).toEqual []
+    it 'handles non-arrays', ->
+      expect(Utils.chunk(null, 2)).toEqual []
+
+    it 'handles non-number counts', ->
+      expect(Utils.chunk([1, 2], null)).toEqual []
+
+    it 'handles a count of zero', ->
+      expect(Utils.chunk([1, 2], 0)).toEqual []
+
+    it 'handles a negative count', ->
+      expect(Utils.chunk([1, 2], -1)).toEqual []


Use notifications in the "audio" controller

--- testApp/app/controllers/audio.coffee
+++ testApp/app/controllers/audio.coffee
@@ -9,14 +9,14 @@
     steroids.audio.play {
       path: "clap.wav"
     }, {
-      onSuccess: () -> alert "audio played"
+      onSuccess: () -> notification "audio played"
       onFailure: () -> alert "audio play failed"
     }

   @testPrime: ->
     steroids.audio.prime {
     }, {
-      onSuccess: () -> alert "audio primed"
+      onSuccess: () -> notification "audio primed"
       onFailure: () -> alert "audio prime failed"
     }


Get the app object from the local apps - that's the only place it exists!

--- src/application.coffee
+++ src/application.coffee
@@ -154,7 +154,7 @@
 					knex('app').update(app).where(imageId: app.imageId)
 			# And delete all the ones to remove in one go
 			promises.push(
-				Promise.map(toBeRemoved, (imageId) -> kill(remoteApps[imageId]))
+				Promise.map(toBeRemoved, (imageId) -> kill(apps[imageId]))
 				.then ->
 					knex('app').whereIn('imageId', toBeRemoved).delete()
 			)


Rename progressEscalation in EDSL

Rename progressEscalation in EDSL

--- src/coffee/edsl.coffee
+++ src/coffee/edsl.coffee
@@ -6,14 +6,14 @@
         item: () -> "#{@value} x #{@name}"
         unique: () -> "You have a #{@name}"
       quality: (id, type, name, description, args = {}) ->
-        {value, progress, maxProgress, hasProgress, progressEscalation, visible} = args
+        {value, progress, maxProgress, hasProgress, escalation, visible} = args
         value ?= 0
         progress ?= 0
         maxProgress ?= if hasProgress then 100 else 0
-        progressEscalation ?= 0.10
+        escalation ?= 0.10
         visible ?= true
         qualities.register id, name, type, description, value,
-                           progress, maxProgress, progressEscalation,
+                           progress, maxProgress, escalation,
                            visible
         return
       storylet: (id, title, text, choices...) ->


#11 returning promise

#11 returning promise

--- src/core/router.coffee
+++ src/core/router.coffee
@@ -45,9 +45,12 @@
       headers:headers
       callback:(err,result)-> if err then defer.reject(err) else defer.resolve(result)
       }
-    Timer.enqueue(()->
-      route.stream.push(_message)
-    )
+    if route?
+      Timer.enqueue(()->
+        route.stream.push(_message)
+      )
+    else
+      defer.reject(new Error("The route #{receiver} doesn't exists"))
     defer.promise
   # Retrieves all defined routes for this router
   getAllRoutes:()->


Fix error reporting

Fix error reporting

--- src/core/server.coffee
+++ src/core/server.coffee
@@ -239,7 +239,7 @@
                 response.end()
                 callback null, 200, pluginName
               else
-                callback new Error "View for content '#{ res.filename }' returned invalid response. Expected Buffer or Stream."
+                callback new Error "View for content '#{ content.filename }' returned invalid response. Expected Buffer or Stream."
             else
               callback null, 404, pluginName # not handled, no data from plugin
         else


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/core_object.coffee
+++ src/core_object.coffee
@@ -151,7 +151,7 @@
     ###
     include: (modules...) ->
       for Module in modules
-        Module = Module if Module.isString
+        Module = Module if Module.isString?()
         self.extend Module(I, self)

       return self


MMC1 bugfixes.

MMC1 bugfixes.

--- src/mappers/MMC1Mapper.coffee
+++ src/mappers/MMC1Mapper.coffee
@@ -16,6 +16,7 @@
     resetMapper: ->
         @resetShiftRegister()
         @resetBankRegisters()
+        @resetConstants()
         @synchronizeMapper()

     resetShiftRegister: ->
@@ -27,6 +28,9 @@
         @romBankRegister = 0     # 5-bit - selects lower/upper 16K ROM bank within a 256K page
         @vromBankRegister1 = 0   # 5-bit - selects lower 4K ROM bank
         @vromBankRegister2 = 0   # 5-bit - selects upper 4K ROM bank
+
+    resetConstants: ->
+        @lastROMBank = Math.min 0x0F, (@rom.length >>> 14) - 1

     ###########################################################
     # MMC writing
@@ -68,32 +72,34 @@
             when 3 then @setMirroring Mirroring.HORIZONTAL

     switchROMBanks: ->
-        pageBase = @getROMPage() * 0x100000                        # Base address of one of 256K pages (for 512K and 1024K roms)
-        @lowerROMBankBase = pageBase + @getLowerROMBank() * 0x4000 # 16K $8000-$BFFF (default first ROM bank in 256k)
-        @upperROMBankBase = pageBase + @getUpperROMBank() * 0x4000 # 16K $C000-$FFFF (default last ROM bank in 256k)
+        @lowerROMBankBase = @getLowerROMBank() * 0x4000 # 16K $8000-$BFFF (default first ROM bank in 256k)
+        @upperROMBankBase = @getUpperROMBank() * 0x4000 # 16K $C000-$FFFF (default last ROM bank in 256k)

-    getROMPage: ->
+
+    getLowerROMBank: ->
+        romBankBase = @getROMBankBase()
+        switch (@controllRegister & 0x0C) >>> 2
+            when 3 then romBankBase | @romBankRegister & 0x0F # Selected 16K ROM bank
+            when 2 then romBankBase                           # First 16K ROM bank
+            else        romBankBase | @romBankRegister & 0x0E # Selected 32K ROM bank (first half)
+
+    getUpperROMBank: ->
+        romBankBase = @getROMBankBase()
+        switch (@controllRegister & 0x0C) >>> 2
+            when 3 then romBankBase | @lastROMBank                   # Last 16K ROM bank
+            when 2 then romBankBase | @romBankRegister & 0x0F        # Selected 16K ROM bank
+            else        romBankBase | @romBankRegister & 0x0E | 0x01 # Selected 32K ROM bank (second half)
+
+    getROMBankBase: ->
         if @rom.length <= 0x40000
             0                                  # First (and only one) 256K page on 256K ROM
         else if @rom.length <= 0x80000
-            (@vromBankRegister1 & 0x10) >>> 4   # First or second 256K page on 512K ROM
+            @vromBankRegister1 & 0x10          # First or second 256K page on 512K ROM
         else if @controllRegister & 0x10
-            (@vromBankRegister2 & 0x10) >>> 3 | # One of four 256K pages on 1024K ROM
-            (@vromBankRegister1 & 0x10) >>> 4
+            (@vromBankRegister2 & 0x10) << 1 | # One of four 256K pages on 1024K ROM
+            (@vromBankRegister1 & 0x10)
         else
-            (@vromBankRegister1 & 0x10) >>> 3   # First or third 256K page on 1024K ROM
-
-    getLowerROMBank: ->
-        switch (@controllRegister & 0x0C) >>> 2
-            when 3 then @romBankRegister & 0x0F # Selected 16K ROM bank
-            when 2 then 0                       # First 16K ROM bank
-            else        @romBankRegister & 0x0E # Selected 32K ROM bank (first half)
-
-    getUpperROMBank: ->
-        switch (@controllRegister & 0x0C) >>> 2
-            when 3 then (@rom.length >>> 14) - 1      # Last 16K ROM bank
-            when 2 then @romBankRegister & 0x0F       # Selected 16K ROM bank
-            else        (@romBankRegister & 0x0E) + 1 # Selected 32K ROM bank (second half)
+            (@vromBankRegister1 & 0x10) << 1   # First or third 256K page on 1024K ROM

     switchVROMBanks: ->
         @lowerVROMBankBase = @getLowerVROMBank() * 0x1000 # 4K $0000-$0FFF
@@ -101,15 +107,15 @@

     getLowerVROMBank: ->
         if @controllRegister & 0x10
-            (@vromBankRegister1 & 0x1F) # Selected 4K VROM bank
+            @vromBankRegister1 & 0x1F # Selected 4K VROM bank
         else
-            (@vromBankRegister1 & 0x1E) # Selected 8K VROM bank (first half)
+            @vromBankRegister1 & 0x1E # Selected 8K VROM bank (first half)

     getUpperVROMBank: ->
         if @controllRegister & 0x10
-            (@vromBankRegister2 & 0x1F)     # Selected 4K VROM bank
+            @vromBankRegister2 & 0x1F        # Selected 4K VROM bank
         else
-            (@vromBankRegister1 & 0x1E) + 1 # Selected 8K VROM bank (second half)
+            @vromBankRegister1 & 0x1E | 0x01 # Selected 8K VROM bank (second half)

     ###########################################################
     # ROM reading


Module typo fixed

Module typo fixed

--- src/module/Error/index.coffee
+++ src/module/Error/index.coffee
@@ -9,7 +9,7 @@

 		class Module.Controller extends Controller
 			handlers:
-				onShowError: ['module/error/view/Error']
+				onShowError: ['module/Error/view/Error']

 			view: null


Add boxes to object3D as a pyramid

Add boxes to object3D as a pyramid

--- src/modules/PlayGround.coffee
+++ src/modules/PlayGround.coffee
@@ -53,8 +53,6 @@
     @meshes.crate_20 = new Objects.TexturedCube(30, 30, 30, textures.woodCrate, false)
     @meshes.crate_21 = new Objects.TexturedCube(30, 30, 30, textures.woodCrate, false)

-
-    @meshes.small_crate = new Objects.TexturedCube(15, 15, 15, textures.woodCrate, false)
     @meshes.small_crate_2 = new Objects.TexturedCube(15, 15, 15, textures.woodCrate, false)
     @meshes.small_crate_3 = new Objects.TexturedCube(15, 15, 15, textures.woodCrate, false)
     @meshes.small_crate_4 = new Objects.TexturedCube(15, 15, 15, textures.woodCrate, false)
@@ -67,6 +65,7 @@
     @meshes.small_crate_11 = new Objects.TexturedCube(15, 15, 15, textures.woodCrate, false)
     @meshes.small_crate_12 = new Objects.TexturedCube(15, 15, 15, textures.woodCrate, false)
     @meshes.small_crate_13 = new Objects.TexturedCube(15, 15, 15, textures.woodCrate, false)
+
     @meshes.small_crate_14 = new Objects.TexturedCube(15, 15, 15, textures.woodCrate, false)
     @meshes.small_crate_15 = new Objects.TexturedCube(15, 15, 15, textures.woodCrate, false)
     @meshes.small_crate_16 = new Objects.TexturedCube(15, 15, 15, textures.woodCrate, false)
@@ -91,6 +90,10 @@
     @meshes.small_crate_35 = new Objects.TexturedCube(15, 15, 15, textures.woodCrate, false)
     @meshes.small_crate_36 = new Objects.TexturedCube(15, 15, 15, textures.woodCrate, false)
     @meshes.small_crate_37 = new Objects.TexturedCube(15, 15, 15, textures.woodCrate, false)
+    @meshes.small_crate_38 = new Objects.TexturedCube(15, 15, 15, textures.woodCrate, false)
+
+    @pyramid = new THREE.Object3D
+    @pyramid.add(@meshes["small_crate_#{i}"]) for i in [14..38]

     @meshes.container = new Objects.TexturedCube(60, 60, 120, textures.metal, true, 2, 1)
     @meshes.container_2 = new Objects.TexturedCube(60, 60, 120, textures.metal, true, 2, 1)
@@ -173,7 +176,6 @@
     @meshes.crate_20.position.set(-307, 75, -0)
     @meshes.crate_21.position.set(395, 15, 262)

-
     @meshes.small_crate_2.position.set(-254.5, 7.5, 0)
     @meshes.small_crate_3.position.set(-254.5, 22.5, 0)
     @meshes.small_crate_4.position.set(-254.5, 37.5, 0)
@@ -187,8 +189,7 @@
     @meshes.small_crate_12.position.set(253, 22.5, 278)
     @meshes.small_crate_13.position.set(268, 7.5, 278)

-
-    @meshes.small_crate.position.set(0, 37.5, 0)
+    # Pyramid
     @meshes.small_crate_14.position.set(15, 22.5, 0)
     @meshes.small_crate_15.position.set(0, 22.5, 15)
     @meshes.small_crate_16.position.set(15, 22.5, 15)
@@ -213,7 +214,10 @@
     @meshes.small_crate_35.position.set(-15, 7.5, -30)
     @meshes.small_crate_36.position.set(0, 7.5, -30)
     @meshes.small_crate_37.position.set(15, 7.5, -30)
-
+    @meshes.small_crate_38.position.set(0, 37.5, 0)
+
+    @pyramid.traverse (mesh) ->
+      mesh.position.z += 100 if mesh instanceof THREE.Mesh

     @meshes.container.position.set(350, 30, 350)
     @meshes.container_2.position.set(-250, 30, -352)
@@ -239,184 +243,8 @@
     # Shadows
     @meshes.plane.receiveShadow = true

-    @meshes.planeFront.castShadow = true
-    @meshes.planeFront.receiveShadow = true
-    @meshes.planeBack.castShadow = true
-    @meshes.planeBack.receiveShadow = true
-    @meshes.planeLeft.castShadow = true
-    @meshes.planeLeft.receiveShadow = true
-    @meshes.planeRight.castShadow = true
-    @meshes.planeRight.receiveShadow = true
-
-    @meshes.container.castShadow = true
-    @meshes.container.receiveShadow = true
-    @meshes.container_2.castShadow = true
-    @meshes.container_2.receiveShadow = true
-    @meshes.container_3.castShadow = true
-    @meshes.container_3.receiveShadow = true
-    @meshes.container_4.castShadow = true
-    @meshes.container_4.receiveShadow = true
-    @meshes.container_5.castShadow = true
-    @meshes.container_5.receiveShadow = true
-    @meshes.container_6.castShadow = true
-    @meshes.container_6.receiveShadow = true
-    @meshes.container_7.castShadow = true
-    @meshes.container_7.receiveShadow = true
-    @meshes.container_8.castShadow = true
-    @meshes.container_8.receiveShadow = true
-    @meshes.container_9.castShadow = true
-    @meshes.container_9.receiveShadow = true
-    @meshes.container_10.castShadow = true
-    @meshes.container_10.receiveShadow = true
-    @meshes.container_11.castShadow = true
-    @meshes.container_11.receiveShadow = true
-    @meshes.container_12.castShadow = true
-    @meshes.container_12.receiveShadow = true
-    @meshes.container_13.castShadow = true
-    @meshes.container_13.receiveShadow = true
-    @meshes.container_14.castShadow = true
-    @meshes.container_14.receiveShadow = true
-    @meshes.container_15.castShadow = true
-    @meshes.container_15.receiveShadow = true
-    @meshes.container_16.castShadow = true
-    @meshes.container_16.receiveShadow = true
-    @meshes.container_17.castShadow = true
-    @meshes.container_17.receiveShadow = true
-
-    @meshes.rock.receiveShadow = true
-    @meshes.rock_2.receiveShadow = true
-    @meshes.rock_2.castShadow = true
-
-    @meshes.crate.castShadow = true
-    @meshes.crate_2.castShadow = true
-    @meshes.crate_3.castShadow = true
-    @meshes.crate_4.castShadow = true
-    @meshes.crate_5.castShadow = true
-    @meshes.crate_6.castShadow = true
-    @meshes.crate_7.castShadow = true
-    @meshes.crate_8.castShadow = true
-    @meshes.crate_9.castShadow = true
-    @meshes.crate_10.castShadow = true
-    @meshes.crate_11.castShadow = true
-    @meshes.crate_12.castShadow = true
-    @meshes.crate_13.castShadow = true
-    @meshes.crate_14.castShadow = true
-    @meshes.crate_15.castShadow = true
-    @meshes.crate_16.castShadow = true
-    @meshes.crate_17.castShadow = true
-    @meshes.crate_18.castShadow = true
-    @meshes.crate_19.castShadow = true
-    @meshes.crate_20.castShadow = true
-    @meshes.crate_21.castShadow = true
-
-
-    @meshes.crate.receiveShadow = true
-    @meshes.crate_2.receiveShadow = true
-    @meshes.crate_3.receiveShadow = true
-    @meshes.crate_4.receiveShadow = true
-    @meshes.crate_5.receiveShadow = true
-    @meshes.crate_6.receiveShadow = true
-    @meshes.crate_7.receiveShadow = true
-    @meshes.crate_8.receiveShadow = true
-    @meshes.crate_9.receiveShadow = true
-    @meshes.crate_10.receiveShadow = true
-    @meshes.crate_11.receiveShadow = true
-    @meshes.crate_12.receiveShadow = true
-    @meshes.crate_13.receiveShadow = true
-    @meshes.crate_14.receiveShadow = true
-    @meshes.crate_15.receiveShadow = true
-    @meshes.crate_16.receiveShadow = true
-    @meshes.crate_17.receiveShadow = true
-    @meshes.crate_18.receiveShadow = true
-    @meshes.crate_19.receiveShadow = true
-    @meshes.crate_20.receiveShadow = true
-    @meshes.crate_21.receiveShadow = true
-
-    @meshes.step.castShadow = true
-    @meshes.step_2.castShadow = true
-    @meshes.step_3.castShadow = true
-    @meshes.step_4.castShadow = true
-    @meshes.step_5.castShadow = true
-    @meshes.step_6.castShadow = true
-    @meshes.step_7.castShadow = true
-    @meshes.step_8.castShadow = true
-    @meshes.step_9.castShadow = true
-
-    @meshes.small_crate.castShadow = true
-    @meshes.small_crate_2.castShadow = true
-    @meshes.small_crate_3.castShadow = true
-    @meshes.small_crate_4.castShadow = true
-    @meshes.small_crate_5.castShadow = true
-    @meshes.small_crate_6.castShadow = true
-    @meshes.small_crate_7.castShadow = true
-    @meshes.small_crate_8.castShadow = true
-    @meshes.small_crate_9.castShadow = true
-    @meshes.small_crate_10.castShadow = true
-    @meshes.small_crate_11.castShadow = true
-    @meshes.small_crate_12.castShadow = true
-    @meshes.small_crate_13.castShadow = true
-    @meshes.small_crate_14.castShadow = true
-    @meshes.small_crate_15.castShadow = true
-    @meshes.small_crate_16.castShadow = true
-    @meshes.small_crate_17.castShadow = true
-    @meshes.small_crate_18.castShadow = true
-    @meshes.small_crate_19.castShadow = true
-    @meshes.small_crate_20.castShadow = true
-    @meshes.small_crate_21.castShadow = true
-    @meshes.small_crate_22.castShadow = true
-    @meshes.small_crate_23.castShadow = true
-    @meshes.small_crate_24.castShadow = true
-    @meshes.small_crate_25.castShadow = true
-    @meshes.small_crate_26.castShadow = true
-    @meshes.small_crate_27.castShadow = true
-    @meshes.small_crate_28.castShadow = true
-    @meshes.small_crate_29.castShadow = true
-    @meshes.small_crate_30.castShadow = true
-    @meshes.small_crate_31.castShadow = true
-    @meshes.small_crate_32.castShadow = true
-    @meshes.small_crate_33.castShadow = true
-    @meshes.small_crate_34.castShadow = true
-    @meshes.small_crate_35.castShadow = true
-    @meshes.small_crate_36.castShadow = true
-    @meshes.small_crate_37.castShadow = true
-
-    @meshes.small_crate.receiveShadow = true
-    @meshes.small_crate_2.receiveShadow = true
-    @meshes.small_crate_3.receiveShadow = true
-    @meshes.small_crate_4.receiveShadow = true
-    @meshes.small_crate_5.receiveShadow = true
-    @meshes.small_crate_6.receiveShadow = true
-    @meshes.small_crate_7.receiveShadow = true
-    @meshes.small_crate_8.receiveShadow = true
-    @meshes.small_crate_9.receiveShadow = true
-    @meshes.small_crate_10.receiveShadow = true
-    @meshes.small_crate_11.receiveShadow = true
-    @meshes.small_crate_12.receiveShadow = true
-    @meshes.small_crate_13.receiveShadow = true
-    @meshes.small_crate_14.receiveShadow = true
-    @meshes.small_crate_15.receiveShadow = true
-    @meshes.small_crate_16.receiveShadow = true
-    @meshes.small_crate_17.receiveShadow = true
-    @meshes.small_crate_18.receiveShadow = true
-    @meshes.small_crate_19.receiveShadow = true
-    @meshes.small_crate_20.receiveShadow = true
-    @meshes.small_crate_21.receiveShadow = true
-    @meshes.small_crate_22.receiveShadow = true
-    @meshes.small_crate_23.receiveShadow = true
-    @meshes.small_crate_24.receiveShadow = true
-    @meshes.small_crate_25.receiveShadow = true
-    @meshes.small_crate_26.receiveShadow = true
-    @meshes.small_crate_27.receiveShadow = true
-    @meshes.small_crate_28.receiveShadow = true
-    @meshes.small_crate_29.receiveShadow = true
-    @meshes.small_crate_30.receiveShadow = true
-    @meshes.small_crate_31.receiveShadow = true
-    @meshes.small_crate_32.receiveShadow = true
-    @meshes.small_crate_33.receiveShadow = true
-    @meshes.small_crate_34.receiveShadow = true
-    @meshes.small_crate_35.receiveShadow = true
-    @meshes.small_crate_36.receiveShadow = true
-    @meshes.small_crate_37.receiveShadow = true
-
+    for key, mesh of @meshes
+      mesh.castShadow = true
+      mesh.receiveShadow = true

 module.exports = PlayGround


Just glob all under images and fonts directories

--- cli/Gruntfile.coffee
+++ cli/Gruntfile.coffee
@@ -374,8 +374,8 @@
     manifest.scripts = grunt.file.expand('app/**/*.js')
     manifest.styles = grunt.file.expand('app/**/*.styl')
     manifest.templates = grunt.file.expand('app/**/*.jade')
-    manifest.images = grunt.file.expand('app/**/*.(png|svg)')
-    manifest.fonts = grunt.file.expand('app/**/*.woff')
+    manifest.images = grunt.file.expand('app/assets/images/**/*')
+    manifest.fonts = grunt.file.expand('app/assets/fonts/**/*')

     content = JSON.stringify(manifest, null, '  ')
     grunt.file.write('component.json', content)


pend shareview test

pend shareview test

--- components/share/test/view.coffee
+++ components/share/test/view.coffee
@@ -22,7 +22,7 @@
   after -> benv.teardown()

   describe '#initialize', ->
-    it '#', ->
+    xit '#', ->
       @view.$('a').first().click()
       @openSpy.called.should.be.ok
       @openSpy.args[0][0].should.equal '#share'


fixed coffeelint errors on workers/social/lib/social/models/group/index.test.coffee

--- workers/social/lib/social/models/group/index.test.coffee
+++ workers/social/lib/social/models/group/index.test.coffee
@@ -747,3 +747,5 @@
 beforeTests()

 runTests()
+
+


Disable Twitter.

Disable Twitter.

--- config/social/jkn2-twitter.coffee
+++ config/social/jkn2-twitter.coffee
@@ -1,29 +1,29 @@

 configs = []

-configs.push
-  name: "Twitter"
-  comment:
-    """
-    In addition to selecting tweets, this also selects the "New Tweets" button at the top of the page.  Scroll
-    to the top and hit "Enter" to load new tweets.
-    """
-  regexps: "^https?://([a-z]+\\.)?twitter.com/?"
-  selectors: do ->
-    selectors = [ "div.new-tweets-bar" ]
-    # for context in [ "div.home-stream", "div.profile-stream", "div.list-stream", "div.search-stream", "li.stream-item" ]
-    for context in [ "li.stream-item" ]
-      for content in [ "div.original-tweet", "li.conversation-root", "li.js-simple-tweet", "div.QuoteTweet" ]
-        selectors.push "#{context} #{content}"
-    selectors
-  activators: [
-    "div.original-tweet a.twitter-timeline-link"
-    # "div.multi-photos"
-    # View/Hide conversation (also view photo).
-    "div.stream-item-footer span.expand-stream-item"
-    "div.stream-item-footer span.collapse-stream-item"
-  ]
-  offset: 140
+## configs.push
+##   name: "Twitter"
+##   comment:
+##     """
+##     In addition to selecting tweets, this also selects the "New Tweets" button at the top of the page.  Scroll
+##     to the top and hit "Enter" to load new tweets.
+##     """
+##   regexps: "^https?://([a-z]+\\.)?twitter.com/?"
+##   selectors: do ->
+##     selectors = [ "div.new-tweets-bar" ]
+##     # for context in [ "div.home-stream", "div.profile-stream", "div.list-stream", "div.search-stream", "li.stream-item" ]
+##     for context in [ "li.stream-item" ]
+##       for content in [ "div.original-tweet", "li.conversation-root", "li.js-simple-tweet", "div.QuoteTweet" ]
+##         selectors.push "#{context} #{content}"
+##     selectors
+##   activators: [
+##     "div.original-tweet a.twitter-timeline-link"
+##     # "div.multi-photos"
+##     # View/Hide conversation (also view photo).
+##     "div.stream-item-footer span.expand-stream-item"
+##     "div.stream-item-footer span.collapse-stream-item"
+##   ]
+##   offset: 140

 require("../../common.js").Common.mkConfigs configs, name: "Twitter"


Make it pass

Make it pass

--- lib/args-match.coffee
+++ lib/args-match.coffee
@@ -1,8 +1,10 @@
 _ = require('lodash')

 module.exports = (expectedArgs, actualArgs) ->
+  return true if _.eq(expectedArgs, actualArgs)
+  return false if expectedArgs.length != actualArgs.length
+
   _.eq expectedArgs, actualArgs, (expected, actual) ->
-    return true if _.eq(expectedArgs, actualArgs)
     _.all expectedArgs, (expectedArg, i) ->
       return true if _.eq(expectedArg, actualArgs[i])
       if _.isFunction(expectedArg?.__matches)


handle image change and automation

handle image change and automation

--- plugins/compute/app/assets/javascripts/compute/cloud_init_configuration.coffee
+++ plugins/compute/app/assets/javascripts/compute/cloud_init_configuration.coffee
@@ -43,8 +43,10 @@
   button.removeClass('disabled');

 @addEventListenerOnSelect= (button) ->
-  $( "#server_image_id" ).unbind "change.automtion"
-  $( "#server_image_id" ).bind "change.automtion", () -> removeOsTypeButtons()
+  $( "#server_vmware_image_id" ).unbind "change"
+  $( "#server_vmware_image_id" ).bind "change", () -> removeOsTypeButtons()
+  $( "#server_baremetal_image_id" ).unbind "change"
+  $( "#server_baremetal_image_id" ).bind "change", () -> removeOsTypeButtons()

 @checkOsType = (button, os_image_option, action_path) ->

@@ -59,6 +61,7 @@
     attachPopover(button, 'Warning', "Missing property 'vmware_ostype' on the image provided. Please follow the steps described in the documentation to upload a compatible image. See customer images documentation. Please choose manually.")
     addEventListenerOnSelect(button)
     addOsTypeButtons(button, action_path)
+
     return
   # get script
   $('a[data-toggle="windowsAutomationScript"]').addClass('disabled')


append an underscore to override relation behaviour in dsl

--- lib/json_utils.coffee
+++ lib/json_utils.coffee
@@ -150,29 +150,34 @@
           else
             relation.cursor(model, field, query).toJSON (err, json) -> result[key] = json; callback(err)

-        else if key is '$select'
-          if _.isString(args)
-            JSONUtils.renderKey model, args, options, (err, json) -> result[args] = json; callback(err)
+        else
+
+          if key.length > 1 and key[key.length-1] is '_'
+            key = key[0..key.length-2]
+
+          if key is '$select'
+            if _.isString(args)
+              JSONUtils.renderKey model, args, options, (err, json) -> result[args] = json; callback(err)
+            else
+              JSONUtils.renderKeys model, args, options, (err, json) -> _.extend(result, json); callback(err)
+
+            # full_name:      'name'
+          else if _.isString(args)
+            JSONUtils.renderKey model, args, options, (err, json) -> result[key] = json; callback(err)
+
+            # can_delete: (photo, options, callback) ->
+          else if _.isFunction(args)
+            args model, options, (err, json) -> result[key] = json; callback(err)
+
+            # is_great: {method: 'isGreatFor', args: [options.user]}
+          else if _.isString(args.method)
+            fn_args = if _.isArray(args.args) then args.args.slice() else (if args.args then [args.args] else [])
+            fn_args.push((err, json) -> result[key] = json; callback())
+            model[args.method].apply(model, fn_args)
+
           else
-            JSONUtils.renderKeys model, args, options, (err, json) -> _.extend(result, json); callback(err)
-
-        # full_name:      'name'
-        else if _.isString(args)
-          JSONUtils.renderKey model, args, options, (err, json) -> result[key] = json; callback(err)
-
-        # can_delete: (photo, options, callback) ->
-        else if _.isFunction(args)
-          args model, options, (err, json) -> result[key] = json; callback(err)
-
-        # is_great: {method: 'isGreatFor', args: [options.user]}
-        else if _.isString(args.method)
-          fn_args = if _.isArray(args.args) then args.args.slice() else (if args.args then [args.args] else [])
-          fn_args.push((err, json) -> result[key] = json; callback())
-          model[args.method].apply(model, fn_args)
-
-        else
-          console.trace "Unknown DSL action: #{key}: #{util.inspect(args)}"
-          return callback(new Error "Unknown DSL action: #{key}: #{util.inspect(args)}")
+            console.trace "Unknown DSL action: #{key}: #{util.inspect(args)}"
+            return callback(new Error "Unknown DSL action: #{key}: #{util.inspect(args)}")

     queue.await (err) -> callback(err, if err then undefined else result)


修改初始发布日志

修改初始发布日志

--- lib/utils/util.coffee
+++ lib/utils/util.coffee
@@ -34,7 +34,7 @@
       "version": "0.0.1",
       "description": "",
       "dependencies": {},
-      "releaseNote": "module init",
+      "releaseNote": "module #{options.moduleName} init",
     }
     """


allow ace to scoll off screen

allow ace to scoll off screen

--- services/web/public/coffee/ide/editor/directives/aceEditor.coffee
+++ services/web/public/coffee/ide/editor/directives/aceEditor.coffee
@@ -163,11 +163,14 @@
 				scope.$watch "readOnly", (value) ->
 					editor.setReadOnly !!value

+				editor.setOption("scrollPastEnd", true)
+
 				resetSession = () ->
 					session = editor.getSession()
 					session.setUseWrapMode(true)
 					session.setMode("ace/mode/latex")
 					session.setAnnotations scope.annotations
+

 				updateCount = 0
 				onChange = () ->


spacing

spacing

--- src/driver/druidDriver.coffee
+++ src/driver/druidDriver.coffee
@@ -19,10 +19,10 @@
 }

 aggregateToJS = {
-  count: ['0', (a, b) -> "#{a}+#{b}"]
-  sum:   ['0', (a, b) -> "#{a}+#{b}"]
-  min:   ['Infinity',  (a, b) -> "Math.min(#{a},#{b})"]
-  max:   ['-Infinity', (a, b) -> "Math.max(#{a},#{b})"]
+  count: ['0', (a, b) -> "#{a} + #{b}"]
+  sum:   ['0', (a, b) -> "#{a} + #{b}"]
+  min:   ['Infinity',  (a, b) -> "Math.min(#{a}, #{b})"]
+  max:   ['-Infinity', (a, b) -> "Math.max(#{a}, #{b})"]
 }

 correctSingletonDruidResult = (result) ->
@@ -66,17 +66,17 @@
       when 'is'
         throw new Error("can not filter on specific time") if filter.attribute is @timeAttribute
         varName = @addToContext(context, filter.attribute)
-        "#{varName}==='#{filter.value}'"
+        "#{varName} === '#{filter.value}'"

       when 'in'
         throw new Error("can not filter on specific time") if filter.attribute is @timeAttribute
         varName = @addToContext(context, filter.attribute)
-        filter.values.map((value) -> "#{varName}==='#{value}'").join('||')
+        filter.values.map((value) -> "#{varName} === '#{value}'").join('||')

       when 'contains'
         throw new Error("can not filter on specific time") if filter.attribute is @timeAttribute
         varName = @addToContext(context, filter.attribute)
-        "String(#{varName}).indexOf('#{filter.value}')!==-1"
+        "String(#{varName}).indexOf('#{filter.value}') !== -1"

       when 'not'
         "!(#{@filterToJSHelper(filter.filter, context)})"
@@ -100,7 +100,7 @@
     }

   timelessFilterToDruid: (filter) ->
-    switch filter.type
+    return switch filter.type
       when 'true'
         null

@@ -111,7 +111,7 @@
         {
           type: 'selector'
           dimension: filter.attribute
-          value: filter.value ? '' # In Druid null == '' and null is illegal
+          value: filter.value
         }

       when 'in'
@@ -121,7 +121,7 @@
             return {
               type: 'selector'
               dimension: filter.attribute
-              value: value ? '' # In Druid null == '' and null is illegal
+              value
             }
           ), this)
         }
@@ -145,14 +145,18 @@

       when 'within'
         [r0, r1] = filter.range
-        if typeof r0 is 'number' and typeof r1 is 'number'
-          {
-            type: 'javascript'
-            dimension: filter.attribute
-            function: "function(a){return a=~~a,#{r0}<=a&&a<#{r1};}"
-          }
-        else
+        if typeof r0 isnt 'number' or typeof r1 isnt 'number'
           throw new Error("apply within has to have a numeric range")
+        {
+          type: 'javascript'
+          dimension: filter.attribute
+          function: """
+            function(a) {
+              a = Number(a);
+              return #{r0} <= a && a < #{r1};
+            }
+            """
+        }

       when 'not'
         {
@@ -236,8 +240,7 @@
             function(x) {
               x = Number(x);
               if(isNaN(x)) return null;
-              x = #{floorExpresion};
-              return x;
+              return #{floorExpresion};
             }
             """


jraphical: change sorting semantics

jraphical: change sorting semantics

if the user passes targetOptions.sort, use that sort order rather than the sort order of the edges

--- node_modules_koding/jraphical/lib/module.coffee
+++ node_modules_koding/jraphical/lib/module.coffee
@@ -310,7 +310,7 @@
               , ->
                 if limit
                   [results] = results
-                else
+                else if not targetOptions.sort?
                   sortedResults = []
                   results.forEach (result)->
                     sortedResults[ordered[result.getId()]] = result


Better handling of missing protagonist.

Better handling of missing protagonist.

--- scripts/generate-samples-ast.coffee
+++ scripts/generate-samples-ast.coffee
@@ -6,11 +6,8 @@
 try
   protagonist = require 'protagonist'
 catch
-  protagonist = null
-
-
-unless protagonist
-  throw new Error "You need to install the latest Protagonist first by running 'npm install protagonist-experimental'."
+  console.error "You need to install the latest Protagonist first by running 'npm install protagonist-experimental'."
+  process.exit 1

 formatsDir = path.resolve __dirname, '..', 'test', 'formats'


add random string to smoke tests to avoid collision

--- services/clsi/test/smoke/coffee/SmokeTests.coffee
+++ services/clsi/test/smoke/coffee/SmokeTests.coffee
@@ -6,10 +6,14 @@

 buildUrl = (path) -> "http://#{Settings.internal.clsi.host}:#{Settings.internal.clsi.port}/#{path}"

+random = require("crypto").randomBytes(4).toString("hex")
+
+url = buildUrl("project/smoketest-#{random}/compile")
+
 describe "Running a compile", ->
 	before (done) ->
 		request.post {
-			url: buildUrl("project/smoketest/compile")
+			url: url
 			json:
 				compile:
 					resources: [


CustomScrollView: stop propagation for a natural behavior

--- src/components/scrollview/customscrollviewinner.coffee
+++ src/components/scrollview/customscrollviewinner.coffee
@@ -23,14 +23,16 @@
     x     = deltaX
     y     = deltaY

-    resX  = if x isnt 0 and @getScrollWidth() > @getWidth()
+    resX  = if x isnt 0 and @getScrollWidth() > @horizontalThumb.getTrackSize()
     then  @_scrollHorizontally {speed, velocity : x}
     else  no
-    resY  = if y isnt 0 and @getScrollHeight() > @getHeight()
+    resY  = if y isnt 0 and @getScrollHeight() > @verticalThumb.getTrackSize()
     then  @_scrollVertically {speed, velocity : y}
     else  no

     stop  = if Math.abs(x) > Math.abs(y) then resX else resY
+
+    KD.utils.stopDOMEvent event  unless stop

     return !stop


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- source/javascripts/_test/string_extensions.coffee
+++ source/javascripts/_test/string_extensions.coffee
@@ -48,8 +48,7 @@

 test "#toInt", ->
   equals "31.3".toInt(), 31
-
-  equals "31.3".toInt(), 31
+  equals "31.".toInt(), 31

 test "#underscore", ->
   equals "Pro-tip".underscore(), "pro_tip"


Print verbose msg upon empty dataset

Print verbose msg upon empty dataset

Fix logic error: Instead of printing an error, display verbose_msg if IP address could not be found in dataset.

--- src/scripts/virustotal.coffee
+++ src/scripts/virustotal.coffee
@@ -100,10 +100,11 @@
                 """

                 msg.send summary
+
               else
-                msg.send "Error: Couldn't access #{vt_url}."
-
+                msg.send vt_json.verbose_msg
+
             else
-              msg.send vt_json.verbose_msg
+              msg.send "Error: Couldn't access #{vt_url}."
     else
       msg.send "VirusTotal API key not configured. Get one at https://www.virustotal.com/en/user/ in the API tab"


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/flickerable.coffee
+++ src/flickerable.coffee
@@ -2,7 +2,9 @@
 The `Flickerable` module provides a method to flicker a sprite between solid and a given opacity (alpha value).

     player = GameObject
-      x:
+      x: 40
+      y: 60
+

 @name Flickerable
 @module


adding elapsedTime

adding elapsedTime

--- src/game_object.coffee
+++ src/game_object.coffee
@@ -133,7 +133,7 @@
     update: (elapsedTime) ->
       if I.active
         self.trigger('step')
-        self.trigger('update')
+        self.trigger 'update', elapsedTim
         I.age += 1

       I.active


[FIX] Most picked in RAT progress report sorts correctly" Closes sixthedge/opentbl#83

--- src/thinkspace/client/thinkspace-readiness-assurance-instructor/addon/components/progress_report/analytics/standard/question.coffee
+++ src/thinkspace/client/thinkspace-readiness-assurance-instructor/addon/components/progress_report/analytics/standard/question.coffee
@@ -40,7 +40,7 @@

   update_most_picked: ->
     choices     = @get('choices')
-    sorted      = choices.sortBy('total')
+    sorted      = choices.sortBy('total').reverse()
     most_picked = [sorted.shift(), sorted.shift()]
     @set('most_picked', most_picked)


Reverted document store provider to return stringified json when loading

--- src/code/providers/document-store-provider.coffee
+++ src/code/providers/document-store-provider.coffee
@@ -171,7 +171,7 @@
       xhrFields:
         withCredentials: true
       success: (data) ->
-        callback null, data.content
+        callback null, JSON.stringify data
       error: ->
         callback "Unable to load "+metadata.name


reverting last change for now until get a chance to do this right.

--- src/smc-webapp/console.coffee
+++ src/smc-webapp/console.coffee
@@ -866,7 +866,7 @@
         # We ignore any data coming out of the terminal for
         # a little while after rerendering, to allow it to fully
         # re-render.  See https://github.com/sagemathinc/cocalc/issues/1269
-        @_ignore_timeout = setTimeout(done, 15)
+        @_ignore_timeout = setTimeout(done, 75)

     resize_terminal: () =>
         # Determine size of container DOM.


add_explanation_textteaching

--- snippets/language-coursescript.cson
+++ snippets/language-coursescript.cson
@@ -567,7 +567,7 @@
       [TYPE P2T]
       ID: $1
       Pic: $2
-      Opts:
+      Opts:
       Text(checked=true): $3
       Text: $4
       Text: $5
@@ -648,4 +648,25 @@
       Text(checked=true): $6
       Text: $7
       Text: $8
+     """
+    'explanation':
+        'prefix': 'explanation'
+        'body': """
+        \"""
+        explanation:""
+        \"""
       """
+    'PreTextTeaching':
+      'prefix': 'PreTextTeaching'
+      'body':"""
+        [TYPE PreDialoguePlay]
+        ID: $1
+        text_teaching:
+        - title: $2
+          paragraph: $3
+        - title: $4
+          bullet_points:
+          - $5
+          - $6
+          - $7
+       """


Change data table to use table header cell class for order


Bootstrap: Add missing salaries generic export modal

--- app/assets/javascripts/app/controllers/salaries/salaries.js.coffee
+++ app/assets/javascripts/app/controllers/salaries/salaries.js.coffee
@@ -27,7 +27,7 @@
     'click button[name=salaries-salary-check-as-paid]'      : 'check_as_paid'
     'click button[name=salaries-salary-copy]'               : 'copy_reference'
     'click button[name=salaries-salary-destroy]'            : 'destroy'
-    'click button[name=salaries-export]'                    : 'stack_export_window'
+    'click button[name=salaries-export]'                    : 'stack_export_generic'
     'click button[name=salaries-export-to-accounting]'      : 'stack_export_to_accounting_window'
     'click button[name=salaries-export-to-ocas]'            : 'stack_export_to_ocas_window'
     'click button[name=salaries-export-to-eLohnausweisSSK]' : 'stack_export_to_eLohnausweisSSK_window'
@@ -72,6 +72,19 @@
       title: title
       message: message

+  stack_export_generic: (e) ->
+    e.preventDefault()
+
+    win = $("<div class='modal fade' id='salaries-export-generic-modal' tabindex='-1' role='dialog' />")
+    # render partial to modal
+    modal = JST["app/views/helpers/modal"]()
+    win.append modal
+    win.modal(keyboard: true, show: false)
+
+    controller = new App.ExportSalaries({el: win.find('.modal-content')})
+    win.modal('show')
+    controller.activate()
+
   stack_export_to_accounting_window: (e) ->
     e.preventDefault()


:lipstick:

:lipstick:

--- spec/atom/global-keymap-spec.coffee
+++ spec/atom/global-keymap-spec.coffee
@@ -2,10 +2,10 @@
 $ = require 'jquery'

 describe "GlobalKeymap", ->
-  handler = null
+  keymap = null

   beforeEach ->
-    handler = new GlobalKeymap
+    keymap = new GlobalKeymap

   describe "handleKeyEvent", ->
     fragment = null
@@ -13,8 +13,8 @@
     insertCharHandler = null

     beforeEach ->
-      handler.bindKeys '.command-mode', 'x': 'deleteChar'
-      handler.bindKeys '.insert-mode', 'x': 'insertChar'
+      keymap.bindKeys '.command-mode', 'x': 'deleteChar'
+      keymap.bindKeys '.insert-mode', 'x': 'insertChar'

       fragment = $ """
         <div class="command-mode">
@@ -31,48 +31,52 @@

     describe "when the event's target node matches a selector with a matching binding", ->
       it "triggers the command event associated with that binding on the target node", ->
-        handler.handleKeyEvent(keypressEvent('x', target: fragment[0]))
+        keymap.handleKeyEvent(keypressEvent('x', target: fragment[0]))
         expect(deleteCharHandler).toHaveBeenCalled()
         expect(insertCharHandler).not.toHaveBeenCalled()

         deleteCharHandler.reset()
         fragment.removeClass('command-mode').addClass('insert-mode')

-        handler.handleKeyEvent(keypressEvent('x', target: fragment[0]))
+        keymap.handleKeyEvent(keypressEvent('x', target: fragment[0]))
         expect(deleteCharHandler).not.toHaveBeenCalled()
         expect(insertCharHandler).toHaveBeenCalled()
+
+    describe "when no binding matches the event", ->
+      it "returns false", ->
+        expect(keymap.handleKeyEvent(keypressEvent('0', target: fragment[0]))).toBe(false)

     describe "when the event's target node *descends* from a selector with a matching binding", ->
       it "triggers the command event associated with that binding on the target node", ->
         target = fragment.find('.child-node')[0]
-        handler.handleKeyEvent(keypressEvent('x', target: target))
+        keymap.handleKeyEvent(keypressEvent('x', target: target))
         expect(deleteCharHandler).toHaveBeenCalled()
         expect(insertCharHandler).not.toHaveBeenCalled()

         deleteCharHandler.reset()
         fragment.removeClass('command-mode').addClass('insert-mode')

-        handler.handleKeyEvent(keypressEvent('x', target: target))
+        keymap.handleKeyEvent(keypressEvent('x', target: target))
         expect(deleteCharHandler).not.toHaveBeenCalled()
         expect(insertCharHandler).toHaveBeenCalled()

     describe "when the event's target node descends from *multiple* selectors with a matching binding", ->
       it "only triggers bindings on selectors associated with the closest ancestor node", ->
-        handler.bindKeys '.child-node', 'x': 'foo'
+        keymap.bindKeys '.child-node', 'x': 'foo'
         fooHandler = jasmine.createSpy 'fooHandler'
         fragment.on 'foo', fooHandler

         target = fragment.find('.grandchild-node')[0]
-        handler.handleKeyEvent(keypressEvent('x', target: target))
+        keymap.handleKeyEvent(keypressEvent('x', target: target))
         expect(fooHandler).toHaveBeenCalled()
         expect(deleteCharHandler).not.toHaveBeenCalled()
         expect(insertCharHandler).not.toHaveBeenCalled()

     describe "when the event bubbles to a node that matches multiple selectors", ->
       it "triggers the binding for the most specific selector", ->
-        handler.bindKeys 'div .child-node', 'x': 'foo'
-        handler.bindKeys '.command-mode .child-node', 'x': 'baz'
-        handler.bindKeys '.child-node', 'x': 'bar'
+        keymap.bindKeys 'div .child-node', 'x': 'foo'
+        keymap.bindKeys '.command-mode .child-node', 'x': 'baz'
+        keymap.bindKeys '.child-node', 'x': 'bar'

         fooHandler = jasmine.createSpy 'fooHandler'
         barHandler = jasmine.createSpy 'barHandler'
@@ -82,8 +86,9 @@
         fragment.on 'baz', bazHandler

         target = fragment.find('.grandchild-node')[0]
-        handler.handleKeyEvent(keypressEvent('x', target: target))
+        keymap.handleKeyEvent(keypressEvent('x', target: target))

         expect(fooHandler).not.toHaveBeenCalled()
         expect(barHandler).not.toHaveBeenCalled()
         expect(bazHandler).toHaveBeenCalled()
+


Include the models too

Include the models too

--- app/lib/main.coffee
+++ app/lib/main.coffee
@@ -1,7 +1,8 @@
 require "ember-skeleton/core"
 require "ember-skeleton/store"
 require "ember-skeleton/router"
+require "ember-skeleton/models"
 require "ember-skeleton/controllers"
 require "ember-skeleton/views"

-App.initialize()+App.initialize()