ability to configure default weights

ability to configure default weights

--- @client/opinion_views.coffee
+++ @client/opinion_views.coffee
@@ -10,8 +10,8 @@
 save
   key: 'opinion_views_ui'
   active: "All opinions"
-  visible_attributes: {}
-  selected_vals_for_attribute: {}
+  activated_attributes: {}
+  visible_attribute_values: {}

 window.compose_opinion_views = (opinions, proposal, opinion_views) ->
@@ -152,29 +152,6 @@

-
-
-default_filters =
-  everyone:
-    key: 'everyone'
-    name: 'everyone'
-    pass: (u) -> true
-
-  just_you:
-    key: 'just_you'
-    name: 'Just you'
-    pass: (u) ->
-      user = fetch(u)
-      user.key == fetch('/current_user').user
-
-  by_date:
-    key: 'date'
-    name: 'By date'
-    pass: (u) ->
-      true
-    options: ['Today', 'Past week', 'Custom']
-
-
 window.influence_network = {}
 window.influencer_scores = {}
 influencer_scores_initialized = false
@@ -193,7 +170,6 @@
   influence_network[influenced].influenced_by[influencer] ?= 0
   influence_network[influenced].influenced_by[influencer] += amount

-
 build_influencer_network = ->
   proposals = fetch '/proposals'
   points = fetch '/points'
@@ -232,6 +208,29 @@

   influencer_scores_initialized = true
+
+
+
+
+default_filters =
+  everyone:
+    key: 'everyone'
+    name: 'everyone'
+    pass: (u) -> true
+
+  just_you:
+    key: 'just_you'
+    name: 'Just you'
+    pass: (u) ->
+      user = fetch(u)
+      user.key == fetch('/current_user').user
+
+  by_date:
+    key: 'date'
+    name: 'By date'
+    pass: (u) ->
+      true
+    options: ['Today', 'Past week', 'Custom']

@@ -334,7 +333,7 @@
 toggle_opinion_filter = (view, replace_existing) ->
   _activate_opinion_view(view, 'filter', replace_existing)

-set_opinion_date_filter = (view) ->
+activate_opinion_date_filter = (view) ->
   _activate_opinion_view(view, 'date-filter', true)

@@ -432,7 +431,7 @@
       weight:   (u, opinion, proposal) -> if pass(u, opinion, proposal) then 1 else .1
       name: activated.label

-    set_opinion_date_filter view
+    activate_opinion_date_filter view

   clear_custom_date = ->
     date_toggle_state.start = null
@@ -528,7 +527,7 @@
         toggle_opinion_filter view

-      for attr in ['group_by', 'selected_vals_for_attribute', 'visible_attributes']
+      for attr in ['group_by', 'visible_attribute_values', 'activated_attributes']
         if attr == 'group_by'
           delete opinion_views_ui[attr] if attr of opinion_views_ui
         else
@@ -591,7 +590,9 @@
     reset_to_default_view = ->
       clear_all()

-      dfault = customization('opinion_filters_default')
+      dfault = customization('opinion_views_default')
+      console.log dfault
+
       if !show_others || dfault?.active == 'Just you'
         toggle_opinion_filter default_filters.just_you
         opinion_views_ui.active = 'Just you'
@@ -600,20 +601,28 @@

         opinion_views_ui.active = 'Custom view'

-        # opinion_filters_default follows the format of opinion_views_ui
+        # opinion_views_default follows the format of opinion_views_ui
         # So we'll read the default values and activate the appropriate views.
         attributes = get_participant_attributes_with_defaults()
-        if dfault.selected_vals_for_attribute?
-          for key, vals of dfault.selected_vals_for_attribute
+        if dfault.visible_attribute_values?
+          for key, vals of dfault.visible_attribute_values
             continue if Object.keys(vals).length == 0
             attribute = attributes.find (attr) -> attr?.key == key
-            opinion_views_ui.selected_vals_for_attribute[attribute.key] = vals
-            opinion_views_ui.visible_attributes[attribute.key] = true
+            opinion_views_ui.visible_attribute_values[attribute.key] = vals
+            opinion_views_ui.activated_attributes[attribute.key] = true
             update_view_for_attribute attribute

         if dfault.group_by
           attribute = attributes.find (attr) -> attr?.key == dfault.group_by
-          set_group_by_attribute attribute
+          set_group_by_attribute attribute
+
+
+        if dfault.weights
+          for weight_key in dfault.weights
+            console.log Object.keys(default_weights)
+            continue if weight_key not of default_weights # LIMITATION: does not allow for custom weight
+            v = default_weights[weight_key]
+            toggle_weight v

       else
         opinion_views_ui.active = 'All opinions'
@@ -674,43 +683,38 @@
             right: if needs_expansion then width - @props.style.width
             marginTop: 18

-          if !@local.minimized
-            DIV
-              style:
-                position: 'absolute'
-                left: (document.querySelector('[data-view-state="Custom view"]')?.offsetLeft or 60) + 35 - (if needs_expansion then (@props.style.width + @props.additional_width - width ) else 0)
-                top: -16
-
-              dangerouslySetInnerHTML: __html: """<svg width="25px" height="13px" viewBox="0 0 25 13" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><g id="Page-2" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"><g id="Artboard" transform="translate(-1086.000000, -586.000000)" fill="#FFFFFF" stroke="#979797"><polyline id="Path" points="1087 599 1098.5 586 1110 599"></polyline></g></g></svg>"""
-

           if @local.minimized
             DIV null,
-
-              MinimizedViews
+              NonInteractiveOpinionViews
                 more_views_positioning: @props.more_views_positioning

-
-
           else
-
-            DIV
-              style:
-                # maxWidth: if !has_other_filters then 660
-                border: '1px solid #B6B6B6'
-                borderRadius: 8
-                width: 'fit-content'
-
-                margin: if @props.more_views_positioning == 'centered' then 'auto'
-                float: if @props.more_views_positioning == 'right' then 'right'
-
-
+            triangle_left = (document.querySelector('[data-view-state="Custom view"]')?.offsetLeft or 60) + 35 - (if needs_expansion then (@props.style.width + @props.additional_width - width ) else 0)
+            DIV null,
               DIV
                 style:
-                  padding: '0px 24px'
-
-                OpinionFilters()
-                OpinionWeights()
+                  position: 'absolute'
+                  left: triangle_left
+                  top: -16
+
+                dangerouslySetInnerHTML: __html: """<svg width="25px" height="13px" viewBox="0 0 25 13" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><g id="Page-2" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"><g id="Artboard" transform="translate(-1086.000000, -586.000000)" fill="#FFFFFF" stroke="#979797"><polyline id="Path" points="1087 599 1098.5 586 1110 599"></polyline></g></g></svg>"""
+
+              DIV
+                style:
+                  border: '1px solid #B6B6B6'
+                  borderRadius: 8
+                  width: 'fit-content'
+
+                  margin: if @props.more_views_positioning == 'centered' then 'auto'
+                  float: if @props.more_views_positioning == 'right' then 'right'
+
+
+                DIV
+                  style:
+                    padding: '0px 24px'
+
+                  InteractiveOpinionViews()

@@ -761,16 +765,16 @@
   attributes

-
+# manages opinion_view_ui state around which attributes the user is interacting with
 toggle_attribute_visibility = (attribute) ->
   opinion_views = fetch 'opinion_views'
   opinion_views_ui = fetch 'opinion_views_ui'
-  opinion_views_ui.visible_attributes[attribute.key] = !opinion_views_ui.visible_attributes[attribute.key]
-  if !opinion_views_ui.visible_attributes[attribute.key] && opinion_views_ui.group_by == attribute.key
+  opinion_views_ui.activated_attributes[attribute.key] = !opinion_views_ui.activated_attributes[attribute.key]
+  if !opinion_views_ui.activated_attributes[attribute.key] && opinion_views_ui.group_by == attribute.key
     toggle_group opinion_views.active_views.group_by
     opinion_views_ui.group_by = null

-  opinion_views_ui.selected_vals_for_attribute[attribute.key] = {}
+  opinion_views_ui.visible_attribute_values[attribute.key] = {}
   save opinion_views_ui

   if attribute.key == 'date'
@@ -782,24 +786,23 @@
     delete opinion_views.active_views[attribute.key]
     save opinion_views

-
-
+# Creates a view function on the fly given the values selected for an attribute by a user
 update_view_for_attribute = (attribute) ->
   opinion_views_ui = fetch 'opinion_views_ui'
   attr_key = attribute.key
   # having no selections for an attribute paradoxically means that all values are valid.
   has_one_enabled = false
-  for val,enabled of opinion_views_ui.selected_vals_for_attribute[attr_key]
+  for val,enabled of opinion_views_ui.visible_attribute_values[attr_key]
     has_one_enabled ||= enabled
   if !has_one_enabled
-    opinion_views_ui.selected_vals_for_attribute[attr_key] = {}
+    opinion_views_ui.visible_attribute_values[attr_key] = {}

   pass = (u) ->
     user = fetch(u)
     val_for_user = user.tags[attr_key]
     is_array = Array.isArray(val_for_user)

-    passing_vals = (val for val,enabled of opinion_views_ui.selected_vals_for_attribute[attr_key] when enabled)
+    passing_vals = (val for val,enabled of opinion_views_ui.visible_attribute_values[attr_key] when enabled)

     passes = false
     for passing_val in passing_vals
@@ -823,6 +826,7 @@

   toggle_opinion_filter view, has_one_enabled

+# manages opinion_view_ui state and the view for grouping by an attribute
 set_group_by_attribute = (attribute) ->
   opinion_views_ui = fetch 'opinion_views_ui'
   opinion_views_ui.group_by = attribute.key
@@ -842,8 +846,8 @@
   toggle_group view, true

-OpinionFilters = ReactiveComponent
-  displayName: 'OpinionFilters'
+InteractiveOpinionViews = ReactiveComponent
+  displayName: 'InteractiveOpinionViews'
   render: ->

     opinion_views = fetch 'opinion_views'
@@ -856,16 +860,17 @@
       if v.view_type == 'filter'
         active_filters[k] = v

-
-
-
-
     for attribute, cnt in attributes
-      opinion_views_ui.selected_vals_for_attribute[attribute.key] ?= {}
+      opinion_views_ui.visible_attribute_values[attribute.key] ?= {}

     if opinion_views_ui.group_by
       all_groups = opinion_views.active_views.group_by.options
       group_colors = get_color_for_groups all_groups
+
+
+    activated_weights = get_activated_weights()
+
+

     DIV null,
       if attributes.length > 0
@@ -898,7 +903,7 @@

                   BUTTON
                     title: if shortened then attribute.name
-                    className: "filter opinion_view_button #{if opinion_views_ui.visible_attributes[attribute.key] then 'active' else ''}"
+                    className: "filter opinion_view_button #{if opinion_views_ui.activated_attributes[attribute.key] then 'active' else ''}"
                     onClick: -> toggle_attribute_visibility(attribute)
                     onKeyDown: (e) =>
                       if e.which == 13 || e.which == 32 # ENTER or SPACE
@@ -911,13 +916,13 @@
                           position: 'relative'
                           top: 2
                           marginRight: 7
-                        attribute.icon?(opinion_views_ui.visible_attributes[attribute.key])
+                        attribute.icon?(opinion_views_ui.activated_attributes[attribute.key])

                     attr_name

       for attribute, cnt in attributes
-        continue if !opinion_views_ui.visible_attributes[attribute.key]
+        continue if !opinion_views_ui.activated_attributes[attribute.key]
         do (attribute) =>
           DIV
             className: 'attribute_wrapper'
@@ -937,7 +942,7 @@

                   for val in attribute.options
                     is_grouped = opinion_views_ui.group_by == attribute.key
-                    checked = !!opinion_views_ui.selected_vals_for_attribute[attribute.key][val]
+                    checked = !!opinion_views_ui.visible_attribute_values[attribute.key][val]

                     val_name = val
                     shortened = false
@@ -962,7 +967,7 @@
                               checked: checked
                               onChange: (e) ->
                                 # create a view on the fly for this attribute
-                                opinion_views_ui.selected_vals_for_attribute[attribute.key][val] = e.target.checked
+                                opinion_views_ui.visible_attribute_values[attribute.key][val] = e.target.checked
                                 save opinion_views_ui
                                 update_view_for_attribute(attribute)

@@ -1018,12 +1023,12 @@
               else
                 opinion_views_ui.group_by = null

-              if opinion_views_ui.group_by && (!opinion_views_ui.visible_attributes[opinion_views_ui.group_by] ||  \
-                                  (o for o,val of opinion_views_ui.selected_vals_for_attribute[opinion_views_ui.group_by] when val).length == 0)
+              if opinion_views_ui.group_by && (!opinion_views_ui.activated_attributes[opinion_views_ui.group_by] ||  \
+                                  (o for o,val of opinion_views_ui.visible_attribute_values[opinion_views_ui.group_by] when val).length == 0)
                             # if no attribute value is selected, which mean all are enabled, select them all
-                opinion_views_ui.visible_attributes[opinion_views_ui.group_by] = true
+                opinion_views_ui.activated_attributes[opinion_views_ui.group_by] = true
                 for option in attribute.options
-                  opinion_views_ui.selected_vals_for_attribute[attribute.key][option] = true
+                  opinion_views_ui.visible_attribute_values[attribute.key][option] = true
               save opinion_views_ui

               if opinion_views_ui.group_by
@@ -1045,9 +1050,54 @@
                 OPTION
                   value: idx
                   attribute.name or attribute.question
-
-
-
+
+
+      DIV
+        className: 'opinion_view_row'
+        style:
+          borderTop: '1px dotted #DEDDDD'
+
+        weigh_icon()
+
+        LABEL
+          className: 'opinion_view_name'
+
+          'Weigh by'
+          ':'
+
+        UL
+          style:
+            listStyle: 'none'
+
+          for k,v of default_weights
+            do (k,v) ->
+              LI
+                style:
+                  marginRight: 8
+                  display: 'inline-block'
+
+                BUTTON
+                  'data-tooltip': v.label
+                  className: "weight opinion_view_button #{if activated_weights[k] then 'active' else ''}"
+                  onClick: ->
+                    toggle_weight v
+                  onKeyDown: (e) ->
+                    if e.which == 13 || e.which == 32 # ENTER or SPACE
+                      toggle_weight v
+                      e.preventDefault()
+
+                  if v.icon
+                    v.icon if activated_weights[k] then 'white'
+
+                  SPAN
+                    style:
+                      paddingLeft: if v.icon then 10
+
+                    v.name
+
+
+
+# returns a list of the active opinion weights
 get_activated_weights = ->
   opinion_views = fetch 'opinion_views'

@@ -1058,59 +1108,8 @@
   activated_weights

-OpinionWeights = ReactiveComponent
-  displayName: 'OpinionWeights'
-  render: ->
-    opinion_views = fetch 'opinion_views'
-
-    activated_weights = get_activated_weights()
-    DIV
-      className: 'opinion_view_row'
-      style:
-        borderTop: '1px dotted #DEDDDD'
-
-      weigh_icon()
-
-      LABEL
-        className: 'opinion_view_name'
-
-        'Weigh by'
-        ':'
-
-      UL
-        style:
-          listStyle: 'none'
-
-        for k,v of default_weights
-          do (k,v) ->
-            LI
-              style:
-                marginRight: 8
-                display: 'inline-block'
-
-              BUTTON
-                'data-tooltip': v.label
-                className: "weight opinion_view_button #{if activated_weights[k] then 'active' else ''}"
-                onClick: ->
-                  toggle_weight v
-                onKeyDown: (e) ->
-                  if e.which == 13 || e.which == 32 # ENTER or SPACE
-                    toggle_weight v
-                    e.preventDefault()
-
-                if v.icon
-                  v.icon if activated_weights[k] then 'white'
-
-                SPAN
-                  style:
-                    paddingLeft: if v.icon then 10
-
-                  v.name
-
-
-
-MinimizedViews = ReactiveComponent
-  displayName: 'MinimizedViews'
+NonInteractiveOpinionViews = ReactiveComponent
+  displayName: 'NonInteractiveOpinionViews'
   render: ->

     minimized_views = []
@@ -1120,11 +1119,10 @@
     attributes = get_participant_attributes_with_defaults()

     for attribute, cnt in attributes
-      continue if !opinion_views_ui.visible_attributes[attribute.key]
+      continue if !opinion_views_ui.activated_attributes[attribute.key]

       is_grouped = opinion_views_ui.group_by == attribute.key

-      console.log attribute.key, is_grouped, opinion_views_ui
       checked = []
       unchecked = []
       if attribute.key == 'date'
@@ -1147,7 +1145,7 @@

       else
         for val in attribute.options
-          if !!opinion_views_ui.selected_vals_for_attribute[attribute.key][val]
+          if !!opinion_views_ui.visible_attribute_values[attribute.key][val]
             checked.push val
           else
             unchecked.push val
@@ -1194,7 +1192,7 @@
             delete opinion_views_ui.group_by
             save opinion_views_ui
             toggle_group opinion_views.active_views.group_by, false
-          # else
+
           toggle_attribute_visibility(attribute)


collision with gun also included

collision with gun also included

--- src/lib/Character/Player/Player.coffee
+++ src/lib/Character/Player/Player.coffee
@@ -48,7 +48,15 @@
         for k, v of @state.GameObjects
             if v.type == 'alien'
                 d = Math.sqrt(Math.pow(@sprite.body.x + @sprite.body.width  * @sprite.anchor.x - v.sprite.body.x - v.sprite.body.width  * 0.5, 2) + Math.pow(@sprite.body.y + @sprite.body.height * @sprite.anchor.y - v.sprite.body.y - v.sprite.body.height * 0.5, 2))
-                if d < 70 and v.alive > 0
+                angle = @sprite.angle
+                angle *= Math.PI / 180.0
+                angle += Math.atan(.5 * @sprite.body.width / (.75 * @sprite.body.height))
+                r = Math.sqrt(Math.pow(.5 * @sprite.body.width, 2) + Math.pow(.75 * @sprite.body.height, 2))
+                gunLocation =
+                    x: @sprite.body.x + @sprite.body.width * @sprite.anchor.x + r * Math.sin(angle)
+                    y: @sprite.body.y + @sprite.body.height * @sprite.anchor.y - r * Math.cos(angle)
+                d2 = Math.sqrt(Math.pow(gunLocation.x - v.sprite.body.x - v.sprite.body.width  * 0.5, 2) + Math.pow(gunLocation.y - v.sprite.body.y - v.sprite.body.height * 0.5, 2))
+                if (d < 70 or d2 < 55) and v.alive > 0
                     @destroy()


Set all polling intervals to 250ms

Set all polling intervals to 250ms

--- src/popout_for_youtube.coffee
+++ src/popout_for_youtube.coffee
@@ -1,5 +1,7 @@
 BUTTON_CLASS = 'popout-for-youtube__button'
 HIDDEN_CLASS = "#{BUTTON_CLASS}--hidden"
+
+POLLING_INTERVAL = 250

 class Extension

@@ -66,7 +68,7 @@
     new Promise (resolve)=>
       setInterval =>
         resolve() if @node = document.querySelector('#player video')
-      , 100
+      , POLLING_INTERVAL

 class Button extends Node

@@ -97,6 +99,7 @@
       @styleInterval = setInterval =>
         @setDisplay()
         @setBottomLeftCorner @video.topRightCorner()
+      , POLLING_INTERVAL

   setDisplay: ->
     if @node.style.top == ''
@@ -124,7 +127,7 @@
   whenVideoChanges: (callback)->
     setInterval =>
       callback() if @videoChanged()
-    , 100
+    , POLLING_INTERVAL

   videoChanged: ->
     @getVideoId() != @previousVideoId


Manually close watcher in non-continuous mode.

--- src/pipes/file-reader.coffee
+++ src/pipes/file-reader.coffee
@@ -42,8 +42,8 @@

     start: ->
         super new RSVP.Promise (resolve, reject) =>
-            @watcher = sane @config.basedir, [@config.pattern],
-                persistent: @config.continuous
+            @watcher = sane @config.basedir,
+                glob: [@config.pattern],

             @watcher.on "ready", =>
                 files = []
@@ -74,6 +74,8 @@
                 RSVP.all files.map (f) -> func f
                 ###

+                @watcher.close() if not @config.continuous
+
             @watcher.on "add", (filepath, root, stat) =>
                 return if stat.isDirectory()
                 if @matches filepath


Follow mixin

Follow mixin

--- test/follow.coffee
+++ test/follow.coffee
@@ -28,6 +28,6 @@
   fastEnemy.include(Follow)
   fastEnemy.follow(player)

-  ok fastEnemy.I.velocity.equal(Point(0, -5)), 'fastEnemy should head toward player with velocity Point()'
+  ok fastEnemy.I.velocity.equal(Point(0, -5)), 'fastEnemy should head toward player with velocity Point(0, 5)'

 module()


refactor tests

refactor tests

--- test/hinoki.coffee
+++ test/hinoki.coffee
@@ -1,6 +1,425 @@
 q = require 'q'

 hinoki = require '../src/hinoki'
+
+module.exports = {}
+
+module.exports.parseFunctionArguments =
+
+    'not a function': (test) ->
+        try
+            hinoki.parseFunctionArguments 0
+        catch err
+            test.equals err.message, 'argument must be a function'
+
+        try
+            hinoki.parseFunctionArguments {}
+        catch err
+            test.equals err.message, 'argument must be a function'
+
+        try
+            hinoki.parseFunctionArguments 'a'
+        catch err
+            test.equals err.message, 'argument must be a function'
+
+        test.done()
+
+    '0 dependencies': (test) ->
+        test.deepEqual [], hinoki.parseFunctionArguments ->
+        test.done()
+
+    '1 dependency': (test) ->
+        test.deepEqual ['first'],
+            hinoki.parseFunctionArguments (first) ->
+        test.done()
+
+    '2 dependencies': (test) ->
+        test.deepEqual ['first', 'second'],
+            hinoki.parseFunctionArguments (first, second) ->
+        test.done()
+
+    '3 dependencies': (test) ->
+        test.deepEqual ['first', 'second', 'third'],
+            hinoki.parseFunctionArguments (first, second, third) ->
+        test.done()
+
+module.exports['find with 1 container'] =
+
+    'not found': (test) ->
+        result = hinoki.find [{}], 'id'
+        test.ok not result?
+        test.done()
+
+    'instance found': (test) ->
+        c =
+            instances:
+                a: 5
+
+        result = hinoki.find [c], 'a'
+
+        test.equal result.instance, 5
+        test.equal result.containers.length, 1
+        test.equal result.containers[0], c
+
+        test.done()
+
+    'factory found': (test) ->
+        c =
+            instances: {}
+            factories:
+                a: 5
+
+        result = hinoki.find [c], 'a'
+
+        test.equal result.factory, 5
+        test.equal result.containers.length, 1
+        test.equal result.containers[0], c
+
+        test.done()
+
+module.exports['find with 3 containers'] =
+
+    'not found': (test) ->
+        result = hinoki.find [{}, {}, {}], 'id'
+        test.ok not result?
+        test.done()
+
+    'instance found in 1st container': (test) ->
+        c1 =
+            instances:
+                a: 5
+        c2 = {}
+        c3 = {}
+
+        result = hinoki.find [c1, c2, c3], 'a'
+
+        test.equal result.instance, 5
+        test.equal result.containers.length, 3
+        test.equal result.containers[0], c1
+        test.equal result.containers[1], c2
+        test.equal result.containers[2], c3
+
+        test.done()
+
+    'instance found in 2nd container': (test) ->
+        c1 = {}
+        c2 =
+            instances:
+                a: 5
+        c3 = {}
+
+        result = hinoki.find [c1, c2, c3], 'a'
+
+        test.equal result.instance, 5
+        test.equal result.containers.length, 2
+        test.equal result.containers[0], c2
+        test.equal result.containers[1], c3
+
+        test.done()
+
+    'instance found in 3rd container': (test) ->
+        c1 = {}
+        c2 = {}
+        c3 =
+            instances:
+                a: 5
+
+        result = hinoki.find [c1, c2, c3], 'a'
+
+        test.equal result.instance, 5
+        test.equal result.containers.length, 1
+        test.equal result.containers[0], c3
+
+        test.done()
+
+    'factory found in 1st container': (test) ->
+        c1 =
+            factories:
+                a: 5
+        c2 = {}
+        c3 = {}
+
+        result = hinoki.find [c1, c2, c3], 'a'
+
+        test.equal result.factory, 5
+        test.equal result.containers.length, 3
+        test.equal result.containers[0], c1
+        test.equal result.containers[1], c2
+        test.equal result.containers[2], c3
+
+        test.done()
+
+    'factory found in 2nd container': (test) ->
+        c1 = {}
+        c2 =
+            factories:
+                a: 5
+        c3 = {}
+
+        result = hinoki.find [c1, c2, c3], 'a'
+
+        test.equal result.factory, 5
+        test.equal result.containers.length, 2
+        test.equal result.containers[0], c2
+        test.equal result.containers[1], c3
+
+        test.done()
+
+    'factory found in 3rd container': (test) ->
+        c1 = {}
+        c2 = {}
+        c3 =
+            factories:
+                a: 5
+
+        result = hinoki.find [c1, c2, c3], 'a'
+
+        test.equal result.factory, 5
+        test.equal result.containers.length, 1
+        test.equal result.containers[0], c3
+
+        test.done()
+
+module.exports['inject with 1 container'] =
+
+    'missing factory': (test) ->
+        try
+            hinoki.inject {}, (a) ->
+        catch error
+            test.equals error.message, "missing factory for service 'a'"
+            test.done()
+
+    'one factory': (test) ->
+        container =
+            factories:
+                a: -> 5
+
+        hinoki.inject container, (a) ->
+            test.equals a, 5
+            test.deepEqual container.instances,
+                a: 5
+            test.done()
+
+    'exception in factory': (test) ->
+        container =
+            factories:
+                a: ->
+                    throw new Error 'b'
+
+        try
+            hinoki.inject container, (a) ->
+        catch error
+            test.equals error.message, "exception in factory 'a': Error: b"
+            test.done()
+
+    '1 seed': (test) ->
+        container =
+            instances:
+                a: 5
+
+        hinoki.inject container, (a) ->
+            test.equals a, 5
+            test.done()
+
+    '3 dependencies': (test) ->
+        container =
+            factories:
+                a: -> 1
+                b: (a) -> a + 1
+                c: (a, b) -> a + b + 1
+
+        hinoki.inject container, (a, b, c) ->
+            test.equals a, 1
+            test.equals b, 2
+            test.equals c, 4
+            test.deepEqual container.instances,
+                a: 1
+                b: 2
+                c: 4
+            test.done()
+
+    '1 factory and 2 seeds': (test) ->
+        container =
+            instances:
+                a: 1
+                b: 2
+            factories:
+                c: (a, b) -> a + b + 1
+
+        hinoki.inject container, (a, b, c) ->
+            test.equals a, 1
+            test.equals b, 2
+            test.equals c, 4
+            test.deepEqual container.instances,
+                a: 1
+                b: 2
+                c: 4
+            test.done()
+
+    'circular dependency': (test) ->
+        container =
+            factories:
+                a: (c) ->
+                b: (a) ->
+                c: (a, b) ->
+
+        try
+            hinoki.inject container, (a) ->
+        catch error
+            test.equals error.message, "circular dependency a <- c <- a"
+            test.deepEqual container.instances, {}
+            test.done()
+
+    'self dependency': (test) ->
+        container =
+            factories:
+                a: (a) ->
+
+        try
+            hinoki.inject container, (a) ->
+        catch error
+            test.equals error.message, "circular dependency a <- a"
+            test.deepEqual container.instances, {}
+            test.done()
+
+    'long circular dependency': (test) ->
+        container =
+            factories:
+                a: (b) ->
+                b: (c) ->
+                c: (d) ->
+                d: (e) ->
+                e: (f) ->
+                f: (a) ->
+
+        try
+            hinoki.inject container, (a) ->
+        catch error
+            test.equals error.message, "circular dependency a <- b <- c <- d <- e <- f <- a"
+            test.deepEqual container.instances, {}
+            test.done()
+
+    'one async dependency with success': (test) ->
+        container =
+            factories:
+                a: ->
+                    deferred = q.defer()
+                    q.nextTick ->
+                        deferred.resolve 5
+                    return deferred.promise
+
+        hinoki.inject container, (a) ->
+            test.equals a, 5
+            test.deepEqual container.instances,
+                a: 5
+            test.done()
+
+    'one async dependency with error': (test) ->
+        container =
+            factories:
+                a: ->
+                    deferred = q.defer()
+                    q.nextTick ->
+                        deferred.reject 5
+                    return deferred.promise
+
+        q.onerror = (err) ->
+            test.equals err.message, "error resolving promise returned from factory 'a'"
+            test.deepEqual container.instances, {}
+            test.done()
+
+        hinoki.inject container, (a) ->
+
+    '3 async dependencies': (test) ->
+        container =
+            factories:
+                a: ->
+                    deferred = q.defer()
+                    q.nextTick -> deferred.resolve 1
+                    return deferred.promise
+                b: (a) ->
+                    deferred = q.defer()
+                    q.nextTick -> deferred.resolve a + 1
+                    return deferred.promise
+                c: (a, b) ->
+                    deferred = q.defer()
+                    q.nextTick -> deferred.resolve a + b + 1
+                    return deferred.promise
+
+        hinoki.inject container, (a, b, c) ->
+            test.equals a, 1
+            test.equals b, 2
+            test.equals c, 4
+            test.deepEqual container.instances,
+                a: 1
+                b: 2
+                c: 4
+            test.done()
+
+module.exports['containers can depend on services in containers to the right of them'] = (test) ->
+        container1 =
+            factories:
+                b: (a) ->
+                    a + 1
+
+        container2 =
+            factories:
+                a: -> 1
+
+        hinoki.inject [container1, container2], (b) ->
+            test.equals b, 2
+            test.done()
+
+module.exports['containers can not depend on services in containers to the left of them'] = (test) ->
+        container1 =
+            factories:
+                a: -> 1
+
+        container2 =
+            factories:
+                b: (a) ->
+                    a + 1
+
+        try
+            hinoki.inject [container1, container2], (b) ->
+        catch err
+            test.equals err.message, "missing factory for service 'a'"
+            test.done()
+
+module.exports['inject with 3 containers'] =
+    '3 async dependencies': (test) ->
+        container1 =
+            factories:
+                c: (a, b) ->
+                    deferred = q.defer()
+                    q.nextTick -> deferred.resolve a + b + 1
+                    return deferred.promise
+
+        container2 =
+            factories:
+                b: (a) ->
+                    deferred = q.defer()
+                    q.nextTick -> deferred.resolve a + 1
+                    return deferred.promise
+
+        container3 =
+            factories:
+                a: ->
+                    deferred = q.defer()
+                    q.nextTick -> deferred.resolve 1
+                    return deferred.promise
+
+        hinoki.inject [container1, container2, container3], (a, b, c) ->
+            test.equals a, 1
+            test.equals b, 2
+            test.equals c, 4
+            test.deepEqual container1.instances,
+                c: 4
+            test.deepEqual container2.instances,
+                b: 2
+            test.deepEqual container3.instances,
+                a: 1
+            test.done()

 computationExampleFactories =
     count: (xs) ->
@@ -16,472 +435,61 @@
     variance: (mean, meanOfSquares) ->
         meanOfSquares - mean * mean

-module.exports =
-
-    'parseFunctionArguments':
-
-        'not a function': (test) ->
-            test.throws ->
-                hinoki.parseFunctionArguments 0
-            test.throws ->
-                hinoki.parseFunctionArguments {}
-            test.throws ->
-                hinoki.parseFunctionArguments 'a'
-            test.done()
-
-        '0 dependencies': (test) ->
-            test.deepEqual [], hinoki.parseFunctionArguments ->
-            test.done()
-
-        '1 dependency': (test) ->
-            test.deepEqual ['first'],
-                hinoki.parseFunctionArguments (first) ->
-            test.done()
-
-        '2 dependencies': (test) ->
-            test.deepEqual ['first', 'second'],
-                hinoki.parseFunctionArguments (first, second) ->
-            test.done()
-
-        '3 dependencies': (test) ->
-            test.deepEqual ['first', 'second', 'third'],
-                hinoki.parseFunctionArguments (first, second, third) ->
-            test.done()
-
-    'find with 1 container':
-
-        'not found': (test) ->
-            result = hinoki.find [{}], 'id'
-            test.ok not result?
-            test.done()
-
-        'instance found': (test) ->
-            c =
-                instances:
-                    a: 5
-
-            result = hinoki.find [c], 'a'
-
-            test.equal result.instance, 5
-            test.equal result.containers.length, 1
-            test.equal result.containers[0], c
-
-            test.done()
-
-        'factory found': (test) ->
-            c =
-                instances: {}
-                factories:
-                    a: 5
-
-            result = hinoki.find [c], 'a'
-
-            test.equal result.factory, 5
-            test.equal result.containers.length, 1
-            test.equal result.containers[0], c
-
-            test.done()
-
-    'find with 3 containers':
-
-        'not found': (test) ->
-            result = hinoki.find [{}, {}, {}], 'id'
-            test.ok not result?
-            test.done()
-
-        'instance found in 1st container': (test) ->
-            c1 =
-                instances:
-                    a: 5
-            c2 = {}
-            c3 = {}
-
-            result = hinoki.find [c1, c2, c3], 'a'
-
-            test.equal result.instance, 5
-            test.equal result.containers.length, 3
-            test.equal result.containers[0], c1
-            test.equal result.containers[1], c2
-            test.equal result.containers[2], c3
-
-            test.done()
-
-        'instance found in 2nd container': (test) ->
-            c1 = {}
-            c2 =
-                instances:
-                    a: 5
-            c3 = {}
-
-            result = hinoki.find [c1, c2, c3], 'a'
-
-            test.equal result.instance, 5
-            test.equal result.containers.length, 2
-            test.equal result.containers[0], c2
-            test.equal result.containers[1], c3
-
-            test.done()
-
-        'instance found in 3rd container': (test) ->
-            c1 = {}
-            c2 = {}
-            c3 =
-                instances:
-                    a: 5
-
-            result = hinoki.find [c1, c2, c3], 'a'
-
-            test.equal result.instance, 5
-            test.equal result.containers.length, 1
-            test.equal result.containers[0], c3
-
-            test.done()
-
-        'factory found in 1st container': (test) ->
-            c1 =
-                factories:
-                    a: 5
-            c2 = {}
-            c3 = {}
-
-            result = hinoki.find [c1, c2, c3], 'a'
-
-            test.equal result.factory, 5
-            test.equal result.containers.length, 3
-            test.equal result.containers[0], c1
-            test.equal result.containers[1], c2
-            test.equal result.containers[2], c3
-
-            test.done()
-
-        'factory found in 2nd container': (test) ->
-            c1 = {}
-            c2 =
-                factories:
-                    a: 5
-            c3 = {}
-
-            result = hinoki.find [c1, c2, c3], 'a'
-
-            test.equal result.factory, 5
-            test.equal result.containers.length, 2
-            test.equal result.containers[0], c2
-            test.equal result.containers[1], c3
-
-            test.done()
-
-        'factory found in 3rd container': (test) ->
-            c1 = {}
-            c2 = {}
-            c3 =
-                factories:
-                    a: 5
-
-            result = hinoki.find [c1, c2, c3], 'a'
-
-            test.equal result.factory, 5
-            test.equal result.containers.length, 1
-            test.equal result.containers[0], c3
-
-            test.done()
-
-    'inject with 1 container':
-
-        'missing factory': (test) ->
-            try
-                hinoki.inject {}, (a) ->
-            catch error
-                test.equals error.message, "missing factory for service 'a'"
-                test.done()
-
-        'one factory': (test) ->
-            container =
-                factories:
-                    a: -> 5
-
-            hinoki.inject container, (a) ->
-                test.equals a, 5
-                test.deepEqual container.instances,
-                    a: 5
-                test.done()
-
-        'exception in factory': (test) ->
-            container =
-                factories:
-                    a: ->
-                        throw new Error 'b'
-
-            try
-                hinoki.inject container, (a) ->
-            catch error
-                test.equals error.message, "exception in factory 'a': Error: b"
-                test.done()
-
-        '1 seed': (test) ->
-            container =
-                instances:
-                    a: 5
-
-            hinoki.inject container, (a) ->
-                test.equals a, 5
-                test.done()
-
-        '3 dependencies': (test) ->
-            container =
-                factories:
-                    a: -> 1
-                    b: (a) -> a + 1
-                    c: (a, b) -> a + b + 1
-
-            hinoki.inject container, (a, b, c) ->
-                test.equals a, 1
-                test.equals b, 2
-                test.equals c, 4
-                test.deepEqual container.instances,
-                    a: 1
-                    b: 2
-                    c: 4
-                test.done()
-
-        '1 factory and 2 seeds': (test) ->
-            container =
-                instances:
-                    a: 1
-                    b: 2
-                factories:
-                    c: (a, b) -> a + b + 1
-
-            hinoki.inject container, (a, b, c) ->
-                test.equals a, 1
-                test.equals b, 2
-                test.equals c, 4
-                test.deepEqual container.instances,
-                    a: 1
-                    b: 2
-                    c: 4
-                test.done()
-
-        'circular dependency': (test) ->
-            container =
-                factories:
-                    a: (c) ->
-                    b: (a) ->
-                    c: (a, b) ->
-
-            try
-                hinoki.inject container, (a) ->
-            catch error
-                test.equals error.message, "circular dependency a <- c <- a"
-                test.deepEqual container.instances, {}
-                test.done()
-
-        'self dependency': (test) ->
-            container =
-                factories:
-                    a: (a) ->
-
-            try
-                hinoki.inject container, (a) ->
-            catch error
-                test.equals error.message, "circular dependency a <- a"
-                test.deepEqual container.instances, {}
-                test.done()
-
-        'long circular dependency': (test) ->
-            container =
-                factories:
-                    a: (b) ->
-                    b: (c) ->
-                    c: (d) ->
-                    d: (e) ->
-                    e: (f) ->
-                    f: (a) ->
-
-            try
-                hinoki.inject container, (a) ->
-            catch error
-                test.equals error.message, "circular dependency a <- b <- c <- d <- e <- f <- a"
-                test.deepEqual container.instances, {}
-                test.done()
-
-        'one async dependency with success': (test) ->
-            container =
-                factories:
-                    a: ->
-                        deferred = q.defer()
-                        q.nextTick ->
-                            deferred.resolve 5
-                        return deferred.promise
-
-            hinoki.inject container, (a) ->
-                test.equals a, 5
-                test.deepEqual container.instances,
-                    a: 5
-                test.done()
-
-        'one async dependency with error': (test) ->
-            container =
-                factories:
-                    a: ->
-                        deferred = q.defer()
-                        q.nextTick ->
-                            deferred.reject 5
-                        return deferred.promise
-
-            q.onerror = (err) ->
-                test.equals err.message, "error resolving promise returned from factory 'a'"
-                test.deepEqual container.instances, {}
-                test.done()
-
-            hinoki.inject container, (a) ->
-
-        '3 async dependencies': (test) ->
-            container =
-                factories:
-                    a: ->
-                        deferred = q.defer()
-                        q.nextTick -> deferred.resolve 1
-                        return deferred.promise
-                    b: (a) ->
-                        deferred = q.defer()
-                        q.nextTick -> deferred.resolve a + 1
-                        return deferred.promise
-                    c: (a, b) ->
-                        deferred = q.defer()
-                        q.nextTick -> deferred.resolve a + b + 1
-                        return deferred.promise
-
-            hinoki.inject container, (a, b, c) ->
-                test.equals a, 1
-                test.equals b, 2
-                test.equals c, 4
-                test.deepEqual container.instances,
-                    a: 1
-                    b: 2
-                    c: 4
-                test.done()
-
-    'containers can depend on services in containers to the right of them': (test) ->
-        container1 =
-            factories:
-                b: (a) ->
-                    a + 1
-
-        container2 =
-            factories:
-                a: -> 1
-
-        hinoki.inject [container1, container2], (b) ->
-            test.equals b, 2
-            test.done()
-
-    'containers can not depend on services in containers to the left of them': (test) ->
-        container1 =
-            factories:
-                a: -> 1
-
-        container2 =
-            factories:
-                b: (a) ->
-                    a + 1
-
-        try
-            hinoki.inject [container1, container2], (b) ->
-        catch err
-            test.equals err.message, "missing factory for service 'a'"
-            test.done()
-
-    'inject with 3 containers':
-
-        '3 async dependencies': (test) ->
-            container1 =
-                factories:
-                    c: (a, b) ->
-                        deferred = q.defer()
-                        q.nextTick -> deferred.resolve a + b + 1
-                        return deferred.promise
-
-            container2 =
-                factories:
-                    b: (a) ->
-                        deferred = q.defer()
-                        q.nextTick -> deferred.resolve a + 1
-                        return deferred.promise
-
-            container3 =
-                factories:
-                    a: ->
-                        deferred = q.defer()
-                        q.nextTick -> deferred.resolve 1
-                        return deferred.promise
-
-            hinoki.inject [container1, container2, container3], (a, b, c) ->
-                test.equals a, 1
-                test.equals b, 2
-                test.equals c, 4
-                test.deepEqual container1.instances,
-                    c: 4
-                test.deepEqual container2.instances,
-                    b: 2
-                test.deepEqual container3.instances,
-                    a: 1
-                test.done()
-
-    'examples':
-
-        'computation - ask for count': (test) ->
-            c =
-                factories: computationExampleFactories
-                instances:
-                    xs: [1, 2, 3, 6]
-
-            hinoki.inject c, (count) ->
-                test.equals count, 4
-                test.deepEqual c.instances,
-                    xs: [1, 2, 3, 6]
-                    count: 4
-                test.done()
-
-        'computation - ask for mean': (test) ->
-            c =
-                factories: computationExampleFactories
-                instances:
-                    xs: [1, 2, 3, 6]
-
-            hinoki.inject c, (mean) ->
-                test.equals mean, 3
-                test.deepEqual c.instances,
-                    xs: [1, 2, 3, 6]
-                    count: 4
-                    mean: 3
-                test.done()
-
-        'computation - ask for meanOfSquares': (test) ->
-            c =
-                factories: computationExampleFactories
-                instances:
-                    xs: [1, 2, 3, 6]
-
-            hinoki.inject c, (meanOfSquares) ->
-                test.equals meanOfSquares, 12.5
-                test.deepEqual c.instances,
-                    xs: [1, 2, 3, 6]
-                    count: 4
-                    meanOfSquares: 12.5
-                test.done()
-
-        'computation - ask for variance': (test) ->
-            c =
-                factories: computationExampleFactories
-                instances:
-                    xs: [1, 2, 3, 6]
-
-            hinoki.inject c, (variance) ->
-                test.equals variance, 3.5
-                test.deepEqual c.instances,
-                    xs: [1, 2, 3, 6]
-                    count: 4
-                    mean: 3
-                    meanOfSquares: 12.5
-                    variance: 3.5
-                test.done()
+module.exports['computation example'] =
+
+    'ask for count': (test) ->
+        c =
+            factories: computationExampleFactories
+            instances:
+                xs: [1, 2, 3, 6]
+
+        hinoki.inject c, (count) ->
+            test.equals count, 4
+            test.deepEqual c.instances,
+                xs: [1, 2, 3, 6]
+                count: 4
+            test.done()
+
+    'ask for mean': (test) ->
+        c =
+            factories: computationExampleFactories
+            instances:
+                xs: [1, 2, 3, 6]
+
+        hinoki.inject c, (mean) ->
+            test.equals mean, 3
+            test.deepEqual c.instances,
+                xs: [1, 2, 3, 6]
+                count: 4
+                mean: 3
+            test.done()
+
+    'ask for meanOfSquares': (test) ->
+        c =
+            factories: computationExampleFactories
+            instances:
+                xs: [1, 2, 3, 6]
+
+        hinoki.inject c, (meanOfSquares) ->
+            test.equals meanOfSquares, 12.5
+            test.deepEqual c.instances,
+                xs: [1, 2, 3, 6]
+                count: 4
+                meanOfSquares: 12.5
+            test.done()
+
+    'ask for variance': (test) ->
+        c =
+            factories: computationExampleFactories
+            instances:
+                xs: [1, 2, 3, 6]
+
+        hinoki.inject c, (variance) ->
+            test.equals variance, 3.5
+            test.deepEqual c.instances,
+                xs: [1, 2, 3, 6]
+                count: 4
+                mean: 3
+                meanOfSquares: 12.5
+                variance: 3.5
+            test.done()


overriding jquery validation's url method

--- app/assets/javascripts/admin/gramgoods.js.coffee
+++ app/assets/javascripts/admin/gramgoods.js.coffee
@@ -36,3 +36,7 @@
         this.optional(element) or /^(\d+)(\.\d{1,2})?$/.test(value) && value > 0
     , "Must be in US currency format 0.99")

+    # override jquery.validation's url method, which requires protocol
+    $.validator.addMethod("url", (value, element) ->
+        this.optional(element) or /^(http[s]?:\/\/){0,1}(www\.){0,1}[a-zA-Z0-9\.\-]+\.[a-zA-Z]{2,5}[\.]{0,1}/.test(value)
+    , "Must be a valid URL")


fix including point from expanded point

fix including point from expanded point

Former-commit-id: 842c17fd96ed2e7c6d27b6530129fd0eda352004 [formerly 82ba2854d14630d2f2a45bd22256dd3cc8d87b8b] [formerly f7731096cc742c64f6ba99b3e5ce0c17ac408f51 [formerly f7731096cc742c64f6ba99b3e5ce0c17ac408f51 [formerly 5b60bca8126bc7dce10305d6f91a56d53f3219bb]]]
Former-commit-id: 7ddc27cf5cc392d1f4c972a5f784e010b762d531 [formerly 3639c5ea9f14d77c41c10b7e71df03d8670529d3]
Former-commit-id: 368bb850baaaafc573a12fe82f6c48fb0aa7f59e
Former-commit-id: 509f7e14a6c48a2ddec1734a8b375f5f2a3b9091

--- app/assets/javascripts/views/positionview.coffee
+++ app/assets/javascripts/views/positionview.coffee
@@ -199,47 +199,51 @@

     model = peers.get(id)
     included_point_model = mine.add(model).get(model)
-    $included_point = @$el.find(".m-point-position[data-id='#{included_point_model.id}']")
-    $included_point.css 'visibility', 'hidden'
-
-    item_offset = $item.offset()
-    ip_offset = $included_point.offset()
-    [offsetX, offsetY] = [ip_offset.left - item_offset.left, ip_offset.top - item_offset.top]
-
-    styles = $included_point.getStyles()
-
-    target_props = {
-      color: styles['color'],
-      #fontSize: styles['fontSize']
-      width: styles['width']
-      paddingRight: styles['paddingRight']
-      paddingLeft: styles['paddingLeft']
-      paddingTop: styles['paddingTop']
-      paddingBottom: styles['paddingBottom']
-      background: 'none'
-      border: 'none'
-    }
-    delete target_props['visibility']
-
-    $placeholder = $('
')
-    $placeholder.css {height: $item.outerHeight(), visibility: 'hidden'}
-
-    $item.find('.m-point-author-avatar, .m-point-include-wrap, .m-point-operations').fadeOut(50)
-
-    $wrap = $item.find('.m-point-wrap')
-
-    $wrap.css {position: 'absolute', width: $wrap.outerWidth()}
-
-
-    $placeholder.insertAfter($item)
-
-
-    _.extend target_props, {top: offsetY, left: offsetX, position: 'absolute'}
-    $wrap.css(target_props).delay(600).queue =>
-      $item.fadeOut ->
-        peers.remove(model)
-        $placeholder.remove()
-        $included_point.css 'visibility', ''
+
+    if $item.is('.m-point-unexpanded')
+      $included_point = @$el.find(".m-point-position[data-id='#{included_point_model.id}']")
+      $included_point.css 'visibility', 'hidden'
+
+      item_offset = $item.offset()
+      ip_offset = $included_point.offset()
+      [offsetX, offsetY] = [ip_offset.left - item_offset.left, ip_offset.top - item_offset.top]
+
+      styles = $included_point.getStyles()
+
+      target_props = {
+        color: styles['color'],
+        #fontSize: styles['fontSize']
+        width: styles['width']
+        paddingRight: styles['paddingRight']
+        paddingLeft: styles['paddingLeft']
+        paddingTop: styles['paddingTop']
+        paddingBottom: styles['paddingBottom']
+        background: 'none'
+        border: 'none'
+      }
+      delete target_props['visibility']
+
+      _.extend target_props, {top: offsetY, left: offsetX, position: 'absolute'}
+
+      $placeholder = $('
')
+      $placeholder.css {height: $item.outerHeight(), visibility: 'hidden'}
+
+      $item.find('.m-point-author-avatar, .m-point-include-wrap, .m-point-operations').fadeOut(50)
+
+      $wrap = $item.find('.m-point-wrap')
+      $wrap.css {position: 'absolute', width: $wrap.outerWidth()}
+
+      $placeholder.insertAfter($item)
+
+      $wrap.css(target_props).delay(600).queue =>
+        $item.fadeOut ->
+          peers.remove(model)
+          $placeholder.remove()
+          $included_point.css 'visibility', ''
+    else
+      $(document).trigger('click')
+      peers.remove(model)
+

     ev.stopPropagation()


Use yeoman installdependencies instead of npm install

--- app/generator.coffee
+++ app/generator.coffee
@@ -9,8 +9,8 @@
 	constructor: (args, options, config) ->
 		super args, options, config

-		# @on "end", ->
-		#   @installDependencies skipInstall: options["skip-install"]
+		@on "end", ->
+			@installDependencies skipInstall: options["skip-install"]

 		@pkg = myUtils.loadJSON("../package.json", __dirname)
 		@settings = myUtils.loadJSON("./settings.json", __dirname)
@@ -144,7 +144,6 @@

 		setupGit= =>
 			GitUtils.init(dest())
-			GitUtils.exec 'npm install'

 		# Main
 		setupRepo()


Refactor runner to supply ctx argument - all tests passing

--- src/main/runtime/ReactiveRunner.coffee
+++ src/main/runtime/ReactiveRunner.coffee
@@ -65,7 +65,9 @@

   _userFunctionStream: (func) ->
     {theFunction, functionNames} = JsCodeGenerator.exprFunction func.expr, @_functionInfo()
-    args = [Operations].concat (@_functionArg(n) for n in functionNames)
+    ctx = {}
+    ctx[n] = @_functionArg(n) for n in functionNames
+    args = [Operations, ctx]
     theFunction.apply null, args

   _functionInfo: ->


Adjusted developer profiles

Adjusted developer profiles

You guys need more credit for being awesome. Feel free to change them
back :)

--- app/lib/en-us.coffee
+++ app/lib/en-us.coffee
@@ -159,7 +159,7 @@
         image: '//placehold.it/100.png'
         twitter: 'https://twitter.com/heathvs'
         description: '''
-          UI/UX Designer at the Zooniverse in Chicago.
+          UI/UX Designer at the Zooniverse in Chicago. He makes websites look and feel amazing.
         '''

       brian:
@@ -167,7 +167,7 @@
         image: '//placehold.it/100.png'
         github: 'https://github.com/brian-c'
         description: '''
-          Zooniverse developer at the Adler Planetarium in Chicago.
+          Zooniverse developer at the Adler Planetarium in Chicago. He turns designs into a seamless, click-able reality.
         '''

       michael:
@@ -175,7 +175,7 @@
         image: '//placehold.it/100.png'
         twitter: 'https://twitter.com/michael_parrish'
         description: '''
-          Zooniverse developer at the Adler Planetarium in Chicago.
+          Zooniverse developer at the Adler Planetarium in Chicago. He pwns databases daily. Likes dogs and bourbon.
         '''

     scientists:


Content: home

Content: home

--- app/lib/en-us.coffee
+++ app/lib/en-us.coffee
@@ -4,7 +4,7 @@
   home:
     welcome: 'Welcome To'
     project: "#{ PROJECT_NAME }"
-    tagline: "We need you help to organize images of sunspots based on their complexity!"
+    tagline: "We need your help to organize sunspot images in order of complexity to better understand and predict how the Sun's magnetic activity affects us on Earth."
     callToAction: "Start Ranking"
     prompt: "What are sunspots?"


Updates to Growl Notifications  * the object is removed from the content array on timeout or close  * the collection view takes care of removing the item view (cleans up the dom)  * changed notification binding to be two-way, so when content is removed from collection-view, the GrowlNoticficationManager.notifications array reflects the same list.  * cleanup regarding the comment I made to issue #36 and also implements the PR #53

--- app/scripts/components/GrowlNotifications.coffee
+++ app/scripts/components/GrowlNotifications.coffee
@@ -11,7 +11,7 @@
     Binding to the GrowlNotificationManager's notifications array
     Each of the array element will be rendered as a notification view (see ItemViewClass)
     ###
-    contentBinding: Ember.Binding.oneWay 'Bootstrap.GNM.notifications'
+    contentBinding: 'Bootstrap.GNM.notifications'
     attributeBindings: ['style']
     showTime: 10000

@@ -47,7 +47,7 @@
         @property {Boolean} should the view be opaque now?
         Used for fancy fading purposes.
         ###
-        isOpaue: false
+        isOpaque: false

         ###
         Lifecycle hook - called when view is created.
@@ -68,7 +68,7 @@
             # Be prepared to auto-hide the notification
             @set "timeoutId", setTimeout((->
                 #@send "close"
-                @close()
+                @send("close")
             ).bind(this), @get("parentView.showTime"))

             # Fade in the view.
@@ -117,8 +117,9 @@
             close: ->
                 @set('isOpaque', false)
                 setTimeout (->
-                    @set "content.closed", true
+                    @get('parentView.content').removeObject(@get('content'))
                     clearTimeout @get("timeoutId")
+
                 ).bind(@), 300
     )
 )
@@ -133,18 +134,6 @@
     @property {Array} A global array for storing notification objects.
     ###
     notifications: Ember.A()
-
-    ###
-    @observer Not technically necessary, but cleans up the notifications array when all have been closed
-    ###
-    notificationClosed: (->
-        notifications = @get('notifications')
-        #Don't do anything if there are no notifications.
-        return if not notifications.length
-
-        #If all the notifications have been closed, wipe our list clean so cruft doesn't build up
-        @set('notifications', Em.A()) if @get('notifications').everyBy('closed')
-    ).observes('notifications.@each.closed')

     ###
     An exposed method for pushing new notification.


Remove spurious fibers dependency from test code

--- test/sockjs.coffee
+++ test/sockjs.coffee
@@ -34,8 +34,6 @@

 class WSSocket

-  Fiber = require('fibers')
-
   constructor: (url, callback)->

     # Open a new browserchannel session to the server


typo in test case for #2333

typo in test case for #2333

--- test/strict.coffee
+++ test/strict.coffee
@@ -79,8 +79,8 @@
   strict usingKeys "'\\\\'", "'\\x5c'"
   strict usingKeys "'\\\n0'", "0"
   strict usingKeys "'\\\n0'", "'\\x00'"
-  strict usingKeys "{'\\'a'", "\"'a\""
-  strict usingKeys "{'\\\\a'", "'\\\\a'"
+  strict usingKeys "'\\'a'", "\"'a\""
+  strict usingKeys "'\\\\a'", "'\\\\a'"
   strictOk usingKeys "a", "b"
   strictOk usingKeys "'\"a\"'", "'a'"
   strictOk usingKeys "'\"a\"'", '"\'a\'"'


who's on call will return list as a block rather than individual messages

--- src/scripts/pagerduty.coffee
+++ src/scripts/pagerduty.coffee
@@ -76,7 +76,7 @@

   robot.respond /(pager|major)( me)? incident (.*)$/i, (msg) ->
     msg.finish()
-
+
     if pagerduty.missingEnvironmentForApi(msg)
       return

@@ -570,9 +570,13 @@

     scheduleName = msg.match[1]

+    messages = ["/code"]
     renderSchedule = (s, cb) ->
       withCurrentOncall msg, s, (username, schedule) ->
-        cb null, "* #{username} is on call for #{schedule.name} - https://#{pagerduty.subdomain}.pagerduty.com/schedules##{schedule.id}"
+        cb null, messages.push("* #{username} is on call for #{schedule.name} - https://#{pagerduty.subdomain}.pagerduty.com/schedules##{schedule.id}")
+    setTimeout ( ->
+      msg.send messages.join("\n")
+    ), 1500

     if scheduleName?
       withScheduleMatching msg, scheduleName, (s) ->
@@ -586,7 +590,6 @@
         if err?
           robot.emit 'error', err, msg
           return
-
         if schedules.length > 0
           async.map schedules, renderSchedule, (err, results) ->
             if err?


Don't require 'for' for getting specific on-call schedule

--- src/scripts/pagerduty.coffee
+++ src/scripts/pagerduty.coffee
@@ -372,18 +372,26 @@

   # who is on call?
-  robot.respond /who('s|s| is|se)? (on call|oncall|on-call)( for (.+))?/i, (msg) ->
+  robot.respond /who('s|s| is|se)? (on call|oncall|on-call)( (?:for )?(.+))?/i, (msg) ->
+    scheduleName = msg.match[4]
+
     query = {}
-    if msg.match[4]
-      query['query'] = msg.match[4]
-    pagerDutyGet msg, "/schedules", query, (json) ->
-      schedules = json.schedules
-      if schedules.length > 0
-        for s in schedules
-          withCurrentOncall msg, s, (username, schedule) ->
-            msg.send "* #{username} is on call for #{schedule.name} - https://#{pagerDutySubdomain}.pagerduty.com/schedules##{schedule.id}\n"
-      else
-        msg.send 'No schedules found!'
+    query['query'] = scheduleName if scheduleName?
+
+    displaySchedule = (s) ->
+      withCurrentOncall msg, s, (username, schedule) ->
+        msg.send "* #{username} is on call for #{schedule.name} - https://#{pagerDutySubdomain}.pagerduty.com/schedules##{schedule.id}\n"
+
+    if scheduleName?
+      withScheduleMatching msg, scheduleName, displaySchedule
+    else
+      pagerDutyGet msg, "/schedules", query, (json) ->
+        schedules = json.schedules
+        if schedules.length > 0
+          for s in schedules
+            displaySchedule(s)
+        else
+          msg.send 'No schedules found!'

   parseIncidentNumbers = (match) ->
     match.split(/[ ,]+/).map (incidentNumber) ->


Fix FollowButton view: confirm unfollow

Fix FollowButton view: confirm unfollow

--- assets/javascripts/views/follow_button.js.coffee
+++ assets/javascripts/views/follow_button.js.coffee
@@ -29,13 +29,11 @@
         @is_following = true
         @render()

-        @render()
-
   init: =>
     @elements.form = DOM.querySelector('form', @el)
     @elements.submit = DOM.querySelector('input[type=submit]', @el)

-    @text.confirm = DOM.attr('data-confirm', @elements.submit)
+    @text.confirm = DOM.attr(@elements.submit, 'data-confirm')

     DOM.on(@elements.form, 'submit', @confirmSubmit)


Ignore more test directories

Ignore more test directories

--- build/tasks/build-task.coffee
+++ build/tasks/build-task.coffee
@@ -51,6 +51,12 @@
       path.join('pegjs', 'examples')
       path.join('plist', 'tests')
       path.join('xmldom', 'test')
+      path.join('combined-stream', 'test')
+      path.join('delayed-stream', 'test')
+      path.join('fstream-ignore', 'test')
+      path.join('harmony-collections', 'test')
+      path.join('normalize-package-data', 'test')
+      path.join('npm', 'test')
       path.join('jasmine-reporters', 'ext')
       path.join('build', 'Release', 'obj.target')
       path.join('build', 'Release', 'obj')


make fixes per PR feedback

make fixes per PR feedback

--- src/smc-webapp/projects.cjsx
+++ src/smc-webapp/projects.cjsx
@@ -780,13 +780,13 @@
                 disabled = {@state.title_text == '' or @state.state == 'saving' or @state.create_button_hit == 'with_members_and_internet'}
                 bsStyle  = 'success'
                 onClick  = {=>@create_project_with_members_and_internet()} >
-                Core upgrades
+                <Icon name="arrow-circle-up" /> Core upgrades
             </Button>
             <Button
                 disabled = {@state.title_text == '' or @state.state == 'saving' or @state.create_button_hit == 'with_custom_upgrades'}
                 bsStyle  = 'success'
                 onClick  = {=>@setState(create_button_hit: 'with_custom_upgrades')} >
-                Custom upgrades
+                <Icon name="cog" /> Custom upgrades
             </Button>
             <Button
                 disabled  = {@state.title_text == '' or @state.state == 'saving'}
@@ -869,14 +869,19 @@
                 </Col>

             </Row>
-
+            <Row>
+                <Col sm=5>
+                </Col>
+                <Col sm=7>
+
You can very easily change the title and description at any time later.

+                </Col>
+            </Row>
             <Row>
                 <Col sm=12>
-                    You can very easily change the title and description at any time later.

-                    A project is your own private computational workspace that you can share
+                    {if @state.title_text then @render_create_buttons() else @render_no_title_warning()}
+
A project is your own private computational workspace that you can share
                     with others.
                     {@render_commercial_explanation_of_project() if require('./customize').commercial}

-                    {if @state.title_text then @render_create_buttons() else @render_no_title_warning()}
                     {@render_error()}
                 </Col>
             </Row>


Put chat images in a hidden folder

Put chat images in a hidden folder

--- src/smc-webapp/smc_chat.cjsx
+++ src/smc-webapp/smc_chat.cjsx
@@ -634,7 +634,7 @@

     append_file: (file) ->
         if file.type.indexOf("image") isnt -1
-            final_insertion_text = ""
+            final_insertion_text = ""
         else
             final_insertion_text = "[#{file.name}](#{file.name})"

@@ -687,7 +687,7 @@
                 <Col xs={10} md={11} style={padding:'0px 2px 0px 2px'}>
                     <SMC_Dropwrapper
                         project_id     = {@props.project_id}
-                        dest_path      = {@props.redux.getProjectStore(@props.project_id).get('current_path')}
+                        dest_path      = {misc.normalized_path_join(@props.redux.getProjectStore(@props.project_id).get('current_path'), "/.chat-images")}
                         event_handlers = {complete : @append_file, sending : @start_upload}
                     >
                         <FormGroup>


re-enable the "checks if you really want to exit page" functionality

--- src/smc-webapp/landing.coffee
+++ src/smc-webapp/landing.coffee
@@ -28,7 +28,8 @@
 # The login page
 require('./account')

-require('./last')
+# Enable the exit confirmation functionality -- checks if you really want to exit page
+require('./exit_confirmation')

 # Uncomment the below and everything gets loaded all at once, rather than lazy when
 # other stuff gets opened.  Is faster in some ways and slower in others.
@@ -39,3 +40,7 @@
 # TODO: temporary -- ensure that the help page is rendered: do this once only on load
 # WE will remove this when we have a proper router.
 require('./r_help').render_help_page()
+
+# Should be loaded last -- this checks the url and opens up the relevant page, etc.
+require('./last')
+


give player a stop method

give player a stop method

--- src/scripts/directives/playlist.coffee
+++ src/scripts/directives/playlist.coffee
@@ -16,8 +16,7 @@
                     @current.sound?.togglePause()

                 playTrack: (index) ->
-                    @current.sound?.pause()
-                    $interval.cancel @current.interval
+                    @stop()
                     @current =
                         track: playlist[index]
                         index: index
@@ -62,5 +61,10 @@
                     position = progress * @current.sound.duration
                     @current.position = position
                     @current.sound?.setPosition position
+
+                stop: ->
+                    $interval.cancel @current.interval
+                    @current.sound?.stop()
+                    @current = {}
     }
 ])


add Maria Edgeworth Letters redirect to FEM (#6020)

--- app/router.cjsx
+++ app/router.cjsx
@@ -167,6 +167,7 @@
     <MonorepoRoute path="/projects/bogden/scarlets-and-blues" />
     <MonorepoRoute path="/projects/kmc35/peoples-contest-digital-archive" />
     <MonorepoRoute path="/projects/rachaelsking/corresponding-with-quakers" />
+    <MonorepoRoute path="/projects/mariaedgeworthletters/maria-edgeworth-letters" />

     <Route path="/projects/mschwamb/planet-four/authors" component={() => <ExternalRedirect newUrl='https://authors.planetfour.org/' />} />


Removes circular reference in robot.brain

Removes the circular reference to robot when saving to the brain.

Includes a DRY'er version of saving to the brain.

--- src/scripts/one_four_twentyfour.coffee
+++ src/scripts/one_four_twentyfour.coffee
@@ -14,20 +14,32 @@
 #   dice take <dice letters> - takes dice at given levels
 #   dice stats - displays the stats for the game
 #
-# Author:
+# Authors:
 #   zbowling
+#   sukima

 dieMap = ['A','B','C','D','E','F']

 class BuddhaGame
-  constructor: (@robot,@player) ->
+  constructor: () ->
     @diceLeft = 6
     @diceTaken = []
     @lastDice = []
     @steps = 0
-
-
+
+  toJSON: ->
+    diceLeft:  @diceLeft
+    diceTaken: @diceTaken
+    lastDice:  @lastDice
+    steps:     @steps
+
+  @build: (data) ->
+    buddha = new BuddhaGame
+    for id, value of data
+      buddha[id] = value
+    buddha
+
   rollRemainingDice: () ->
     @lastDice = []
     printlines = ['','','','','','','']
@@ -223,29 +235,30 @@

 class BuddhaLounge
   constructor: (@robot) ->
-    @games = []
-    @playerdata = []
-    @players = []
-
+    @games = {}
+    @playerdata = {}
+    @players = {}
+
     @robot.brain.on 'loaded', =>
       if @robot.brain.data.buddhagames?
-        @games = @robot.brain.data.buddhagames
+        for id, game_data of @robot.brain.data.buddhagames
+          @games[id] = BuddhaGame.build game_data
       if @robot.brain.data.playerdata?
         @playerdata = @robot.brain.data.playerdata
-
+
   startGame: (msg, player) ->
-    game = new BuddhaGame @robot, player
+    game = new BuddhaGame
     msg.reply "\n" + game.roll()
     @games[player.id] = game
-    @robot.brain.data.buddhagames = @games
     if not @players[player.id]?
       @players[player.id] = player
-
-    if not @playerdata[player.id]?
+
+    if not @playerdata[player.id]?
       @playerdata[player.id] = { totalScore: 0, totalGamesStarted: 0, totalGamesFinished: 0, lastScore:0}
     @playerdata[player.id].totalGamesStarted += 1;
-    @robot.brain.data.playerdata = @playerdata
-
+
+    @save()
+
   playsound: (player,sound) ->
     if @robot.bot?
       @robot.bot.Room(player.room).sound sound, (err, data) =>
@@ -280,21 +293,24 @@
         @playerdata[player.id].lastScore = scoreValue.score
         @playerdata[player.id].totalScore = scoreValue.score + @playerdata[player.id].totalScore
         @playerdata[player.id].totalGamesFinished = @playerdata[player.id].totalGamesFinished + 1
-        @robot.brain.data.playerdata = @playerdata
         delete @games[player.id]
     else
       msg.reply "you aren't playing a game."
-
-    @robot.brain.data.buddhagames = @games
-
+
+    @save()
+
   playerstats: (player) ->
     "\n#{player.name}: \n
 \tlast score: #{@playerdata[player.id].lastScore}\n
 \taverage score: #{@playerdata[player.id].totalScore/@playerdata[player.id].totalGamesFinished}\n
 \ttotal games finished: #{@playerdata[player.id].totalGamesFinished}\n
 \ttotal games started: #{@playerdata[player.id].totalGamesStarted}\n"
-
-
+
+
+  save: ->
+    @robot.brain.data.buddhagames = @games
+    @robot.brain.data.playerdata = @playerdata
+
   stats: (msg) ->
     return_str = ""
     for k,v of @players


check for existence of wizardMetadata before using

--- app/assets/javascripts/hat-trick.js.coffee
+++ app/assets/javascripts/hat-trick.js.coffee
@@ -221,7 +221,7 @@
         stepId = htData.wizardMetadata.currentStep.fieldset
         this.buttons[stepId] = htData.wizardMetadata.currentStep.buttons

-      if htData.wizardMetadata.currentStep.repeatOf?
+      if htData.wizardMetadata?.currentStep.repeatOf?
         this.repeatStep(htData.wizardMetadata.currentStep)
       else
         this.showStep(htData.wizardMetadata.currentStep)


added test

added test

--- test/crystal/validate/exports.coffee
+++ test/crystal/validate/exports.coffee
@@ -33,6 +33,18 @@
         (-> new crystal config)
           .should.throw "`exports` must be a `object`, not a `boolean`."

+      it 'should not require a description'
+        # object
+        config = {
+          exports: {
+            ConfigGenerator: {
+              type: 'generator'
+            }
+          }
+        }
+        (-> new crystal config)
+          .should.not.throw "`exports.ConfigGenerator.description` is required."
+
       it 'should require a type', () ->
         # object
         config = {


define devToolsWebContents for use from remote module

--- atom/browser/api/lib/browser-window.coffee
+++ atom/browser/api/lib/browser-window.coffee
@@ -17,6 +17,7 @@
     @setMenu menu if menu?

   @webContents = @getWebContents()
+  @devToolsWebContents = null
   @webContents.once 'destroyed', => @webContents = null

   # Remember the window ID.


Refactored for more readability

Refactored for more readability

--- src/transaction-runner.coffee
+++ src/transaction-runner.coffee
@@ -35,6 +35,7 @@
     async.mapSeries transactions, @configureTransaction.bind(@), (err, results) ->
       transactions = results

+    # Remeainings of functional approach, probs to be eradicated
     addHooks @, transactions, (addHooksError) =>
       return callback addHooksError if addHooksError
       @executeAllTransactions(transactions, @hooks, callback)
@@ -304,6 +305,7 @@
         packageConfig['version'] + \
         " (" + system + ")"

+    # Parse and add headers from the config to the transaction
     if configuration.options.header.length > 0
       for header in configuration.options.header
         splitIndex = header.indexOf(':')
@@ -374,30 +376,33 @@
           @configuration.emitter.emit 'test pass', transaction.test, () ->
     callback()

-  executeTransaction: (transaction, hooks, callback) ->
-    unless callback
-      callback = hooks
-      hooks = null
-
-    configuration = @configuration
-
-
-    # Add length of body if no Content-Length present
-    # Doing here instead of in configureTransaction, because request body can be edited in before hook
-
-    caseInsensitiveRequestHeadersMap = {}
-    for key, value of transaction.request.headers
-      caseInsensitiveRequestHeadersMap[key.toLowerCase()] = key
-
-    if not caseInsensitiveRequestHeadersMap['content-length'] and transaction.request['body'] != ''
-      transaction.request.headers['Content-Length'] = Buffer.byteLength(transaction.request['body'], 'utf8')
-
+  getRequestOptionsFromTransaction: (transaction) ->
     requestOptions =
       host: transaction.host
       port: transaction.port
       path: transaction.fullPath
       method: transaction.request['method']
       headers: transaction.request.headers
+    return requestOptions
+
+  # Add length of body if no Content-Length present
+  setContentLength: (transaction) ->
+    caseInsensitiveRequestHeadersMap = {}
+    for key, value of transaction.request.headers
+      caseInsensitiveRequestHeadersMap[key.toLowerCase()] = key
+
+    if not caseInsensitiveRequestHeadersMap['content-length'] and transaction.request['body'] != ''
+      transaction.request.headers['Content-Length'] = Buffer.byteLength(transaction.request['body'], 'utf8')
+
+  # This is actually doing more some pre-flight and confitional skipping of the transcation
+  # based on the configuration or hooks. TODO rename
+  executeTransaction: (transaction, hooks, callback) =>
+    unless callback
+      callback = hooks
+      hooks = null
+
+    # Doing here instead of in configureTransaction, because request body can be edited in before hook
+    @setContentLength(transaction)

     transaction.startedAt = Date.now() # number in miliseconds (UNIX-like timestamp * 1000 precision)

@@ -408,7 +413,7 @@
       origin: transaction.origin
       startedAt: transaction.startedAt

-    configuration.emitter.emit 'test start', test, () ->
+    @configuration.emitter.emit 'test start', test, () ->

     transaction['results'] ?= {}
     transaction['results']['general'] ?= {}
@@ -422,7 +427,7 @@
       test['results'] = transaction['results']
       test['status'] = 'skip'

-      configuration.emitter.emit 'test skip', test, () ->
+      @configuration.emitter.emit 'test skip', test, () ->
       return callback()

     else if transaction.fail
@@ -435,77 +440,85 @@

       test['results'] = transaction['results']

-      configuration.emitter.emit 'test fail', test, () ->
-      return callback()
-    else if configuration.options['dry-run']
+      @configuration.emitter.emit 'test fail', test, () ->
+      return callback()
+    else if @configuration.options['dry-run']
       logger.info "Dry run, skipping API Tests..."
       transaction.skip = true
       return callback()
-    else if configuration.options.names
+    else if @configuration.options.names
       logger.info transaction.name
       transaction.skip = true
       return callback()
-    else if configuration.options.method.length > 0 and not (transaction.request.method in configuration.options.method)
-      configuration.emitter.emit 'test skip', test, () ->
+    else if @configuration.options.method.length > 0 and not (transaction.request.method in @configuration.options.method)
+      @configuration.emitter.emit 'test skip', test, () ->
       transaction.skip = true
       return callback()
-    else if configuration.options.only.length > 0 and not (transaction.name in configuration.options.only)
-      configuration.emitter.emit 'test skip', test, () ->
+    else if @configuration.options.only.length > 0 and not (transaction.name in @configuration.options.only)
+      @configuration.emitter.emit 'test skip', test, () ->
       transaction.skip = true
       return callback()
     else
-      buffer = ""
-
-      handleRequest = (res) =>
-        res.on 'data', (chunk) ->
-          buffer = buffer + chunk
-
-        res.on 'error', (error) ->
-          if error
-            test.title = transaction.id
-            test.expected = transaction.expected
-            test.request = transaction.request
-            configuration.emitter.emit 'test error', error, test, () ->
-
-          return callback()
-
-        res.once 'end', =>
-
-          # The data models as used here must conform to Gavel.js
-          # as defined in `http-response.coffee`
-          real =
-            statusCode: res.statusCode
-            headers: res.headers
-            body: buffer
-
-          transaction['real'] = real
-
-          @runHooksForData hooks?.beforeEachValidationHooks, transaction, false, () =>
-            return callback(@hookHandlerError) if @hookHandlerError?
-            @runHooksForData hooks?.beforeValidationHooks[transaction.name], transaction, false, () =>
-              return callback(@hookHandlerError) if @hookHandlerError?
-              @validateTransaction test, transaction, callback
-
-
-      transport = if transaction.protocol is 'https:' then https else http
-      if transaction.request['body'] and @isMultipart requestOptions
-        @replaceLineFeedInBody transaction, requestOptions
-
-      try
-        req = transport.request requestOptions, handleRequest
-
-        req.on 'error', (error) ->
+      return @performRequestAndValidate(test, transaction, hooks, callback)
+
+  # An actual HTTP request, before validation hooks triggering
+  # and the the response validation is invoked here
+  performRequestAndValidate: (test, transaction, hooks, callback) ->
+    requestOptions = @getRequestOptionsFromTransaction(transaction)
+    buffer = ""
+
+    handleRequest = (res) =>
+      res.on 'data', (chunk) ->
+        buffer = buffer + chunk
+
+      res.on 'error', (error) =>
+        if error
           test.title = transaction.id
           test.expected = transaction.expected
           test.request = transaction.request
-          configuration.emitter.emit 'test error', error, test, () ->
-          return callback()
-
-        req.write transaction.request['body'] if transaction.request['body'] != ''
-        req.end()
-      catch error
-        configuration.emitter.emit 'test error', error, test, () ->
+          @configuration.emitter.emit 'test error', error, test, () ->
+
         return callback()
+
+      res.once 'end', =>
+        # The data models as used here must conform to Gavel.js
+        # as defined in `http-response.coffee`
+        real =
+          statusCode: res.statusCode
+          headers: res.headers
+          body: buffer
+
+        transaction['real'] = real
+
+        @runHooksForData hooks?.beforeEachValidationHooks, transaction, false, () =>
+          return callback(@hookHandlerError) if @hookHandlerError?
+          @runHooksForData hooks?.beforeValidationHooks[transaction.name], transaction, false, () =>
+            return callback(@hookHandlerError) if @hookHandlerError?
+            @validateTransaction test, transaction, callback
+
+
+    transport = if transaction.protocol is 'https:' then https else http
+    if transaction.request['body'] and @isMultipart requestOptions
+      @replaceLineFeedInBody transaction, requestOptions
+
+    try
+      req = transport.request requestOptions, handleRequest
+
+      req.on 'error', (error) =>
+        test.title = transaction.id
+        test.expected = transaction.expected
+        test.request = transaction.request
+        @configuration.emitter.emit 'test error', error, test, () ->
+        return callback()
+
+      req.write transaction.request['body'] if transaction.request['body'] != ''
+      req.end()
+    catch error
+      test.title = transaction.id
+      test.expected = transaction.expected
+      test.request = transaction.request
+      @configuration.emitter.emit 'test error', error, test, () ->
+      return callback()

   validateTransaction: (test, transaction, callback) ->
     configuration = @configuration


Add extra test for synchronous throws from events

--- test/sequential_executor.spec.coffee
+++ test/sequential_executor.spec.coffee
@@ -85,6 +85,14 @@
     it 'stops emitting if error is returned', ->
       list = []
       @emitter.on 'event1', -> list.push(1)
+      @emitter.on 'event1', -> list.push(2); throw new Error('error')
+      @emitter.on 'event1', -> list.push(3)
+      expect(=> @emitter.emit 'event1').toThrow('error')
+      expect(list).toEqual([1,2])
+
+    it 'stops emitting if error is returned (async)', ->
+      list = []
+      @emitter.on 'event1', -> list.push(1)
       @emitter.onAsync 'event1', (err, done) -> list.push(2); done('ERROR')
       @emitter.on 'event1', -> list.push(3)
       @emitter.emit 'event1', [null], (err) ->


Fixed highlighting on PDFs with watermarks and header segments out of order. Fixes #387

--- client/lib/highlighter.coffee
+++ client/lib/highlighter.coffee
@@ -87,9 +87,29 @@
     return if @highlightsEnabled
     @highlightsEnabled = true

+    @_cleanTextSegments()
+
     # For debugging
     #@_showSegments()
     #@_showTextSegments()
+
+  _cleanTextSegments: =>
+    # We traverse from the end and search for segments which should be before the first segment
+    # and mark them unselectable. The rationale is that those segments which are spatially positioned
+    # before the first segment, but are out-of-order in the array are watermarks or headers and other
+    # elements not connected with the content, but they interfer with highlighting. It seems they are
+    # simply appended at the end so we search them only near the end. We still allow unselectable
+    # segments to be selected in the browser if user is directly over it.
+    # See https://github.com/peerlibrary/peerlibrary/issues/387
+
+    # Few segments can be correctly ordered among those at the end. For example, page numbers.
+    threshold = 5 # segments, currently chosen completely arbritrary (just that it is larger than 1)
+    for segment in @textSegments by -1
+      if segment.boundingBox.left >= @textSegments[0].boundingBox.left and segment.boundingBox.top >= @textSegments[0].boundingBox.top
+        threshold--
+        break if threshold is 0
+        continue
+      segment.unselectable = true

   _distanceX: (position, area) =>
     return Number.POSITIVE_INFINITY unless area
@@ -132,6 +152,17 @@

     left: e.pageX - offset.left
     top: e.pageY - offset.top
+
+  _overTextSegment: (position) =>
+    segmentIndex = -1
+    # We still want to allow unselectable segments to be selected in the
+    # browser if user is directly over it, so we go over all segments here.
+    for segment, index in @textSegments
+      if @_distanceX(position, segment.boundingBox) + @_distanceY(position, segment.boundingBox) is 0
+        segmentIndex = index
+        break
+
+    segmentIndex

   # Finds a text layer segment which is it to the left and up of the given position
   # and has highest index. Highest index means it is latest in the text flow of the
@@ -140,7 +171,7 @@
   # text which flows left to right, top to bottom.
   _findLastLeftUpTextSegment: (position) =>
     segmentIndex = -1
-    for segment, index in @textSegments
+    for segment, index in @textSegments when not segment.unselectable
       # We allow few additional pixels so that position can be slightly to the left
       # of the text segment. This helps when user is with mouse between two columns
       # of text. With this the text segment to the right (in the right column) is
@@ -160,7 +191,7 @@
     closestSegmentIndex = -1
     closestDistance = Number.POSITIVE_INFINITY

-    for segment, index in @textSegments
+    for segment, index in @textSegments when not segment.unselectable
       distance = @_distance position, segment.boundingBox
       if distance < closestDistance
         closestSegmentIndex = index
@@ -226,6 +257,13 @@
   padTextSegments: (e) =>
     position = @_eventToPosition e

+    # First check if we are directly above a text segment
+    segmentIndex = @_overTextSegment position
+
+    if segmentIndex isnt -1
+      @_padTextSegment position, segmentIndex
+      return
+
     # Find latest text layer segment in text flow on the page before the given position
     segmentIndex = @_findLastLeftUpTextSegment position

@@ -233,9 +271,9 @@
     # infinity in this case, so things work out
     if @_distanceY(position, @textSegments[segmentIndex]?.boundingBox) is 0
       # A clear case, we are directly over a segment y-wise. This means that
-      # we are really directly over a segment, or that segment is to the right
-      # of mouse position (because we searched for all segments to the left and
-      # up of the position). In any case, this is the segment we want to pad.
+      # segment is to the left of mouse position (because we searched for
+      # all segments to the left and up of the position and we already checked
+      # if we are directly over a segment). This is the segment we want to pad.
       @_padTextSegment position, segmentIndex
       return

@@ -247,11 +285,20 @@
     # of the page and there are no text segments to the left and up. So we as well
     # do a search from the beginning of the page to the last text segment on the
     # text line just above our position.
-    while @textSegments[segmentIndex + 1]
-      if @textSegments[segmentIndex + 1].boundingBox.top + @textSegments[segmentIndex + 1].boundingBox.height > position.top
-        break
+    # We keep track of the number of skipped unselectable segments to not increase
+    # segmentIndex until we get to a selectable segment again (if we do at all).
+    skippedUnselectable = 0
+    while @textSegments[segmentIndex + skippedUnselectable + 1]
+      segment = @textSegments[segmentIndex + skippedUnselectable + 1]
+      if segment.unselectable
+        skippedUnselectable++
       else
-        segmentIndex++
+        segmentIndex += skippedUnselectable
+        skippedUnselectable = 0
+        if segment.boundingBox.top + segment.boundingBox.height > position.top
+          break
+        else
+          segmentIndex++

     # segmentIndex can still be -1 if there are no text segments before
     # the mouse position, so let's simply find closest segment and pad that.


add button for quicksort

add button for quicksort


fix variable name

fix variable name

--- js/inner/repo.coffee
+++ js/inner/repo.coffee
@@ -21,7 +21,7 @@
       if @fork
         "#{name} (forked)"
       else
-        @name
+        name

     @repoToolTip = @komp =>
       if @fork


Added support for requiring yaml and txt files

--- lib/bundle-builder/bundle.coffee
+++ lib/bundle-builder/bundle.coffee
@@ -113,7 +113,7 @@
         name = name.replace /\\/g, '\\\\'

         switch pathUtils.extname name
-            when '.json'
+            when '.json', '.yaml', '.yml', '.txt'
                 func = "module.exports = #{func};"

         module = moduleScope


Larger refactoring of JSON Schema format.

--- lib/formats/jsonschema-v4.coffee
+++ lib/formats/jsonschema-v4.coffee
@@ -3,68 +3,85 @@
 {resolveType} = require '../typeresolution'

-###########################################################################
-## PROTOTYPE ALERT! This is work in progress as much as it only can be.  ##
-###########################################################################
+listProperties = (objectType, cb) ->
+  props = []
+  for member in (objectType.sections or []) when member.type is 'member'
+    for prop in member.content when prop.type is 'property'
+      props.push prop
+  cb null, props

-handleValue = (value, options, cb) ->
-  resolveType value, (err, typeSpec) ->
-    return cb err if err
-
-    valueType = typeSpec.name  # ignoring nested types for now
-    switch valueType
-      when 'object'
-        return handleObject value, options, cb
-
-      # when 'array'
-      #   do nothing - not implemented yet
-
-    cb null, type: valueType
+resolveProperty = (prop, options, cb) ->
+  async.waterfall [
+    (next) -> handleType prop.content, options, next
+    (schema, next) ->
+      next null,
+        name: prop.content.name.literal
+        schema: schema
+        required: 'required' in (prop.content?.valueDefinition?.typeDefinition?.attributes or [])
+  ], cb

-handleObject = (type, options, cb) ->
-  # prepare a simple array of property objects
-  props = []
-
-  for member in (type.sections or []) when member.type is 'member'
-    for prop in member.content when prop.type is 'property'
-      props.push prop
-
-  # map over that array, get representations of property values and wrap them
-  # with object carrying also property-specific information (name, required, ...)
+resolveProperties = (props, options, cb) ->
   async.map props, (prop, next) ->
-    handleValue prop.content, options, (err, propRepr) ->
-      next err,
-        name: prop.content.name.literal
-        repr: propRepr
-        required: 'required' in (prop.content?.valueDefinition?.typeDefinition?.attributes or [])
-
-  , (err, reprWrappers) ->
-    return cb err if err
-
-    # prepare containers for the final representation of the object
-    propsRepr = {}
-    requiredRepr = []
-
-    # unwrap info for each property and render what needs to be rendered
-    for {name, repr, required} in reprWrappers
-      propsRepr[name] = repr
-      requiredRepr.push name if required
-
-    # build the final object representation and send it to callback
-    repr =
-      type: 'object'
-      properties: propsRepr
-    repr.required = requiredRepr if requiredRepr.length > 0
-    cb null, repr
+    resolveProperty prop, options, next
+  , cb

-transform = (type, options, cb) ->
-  handleObject type, options, (err, repr) ->
+buildObjectSchema = (resolvedProps, options, cb) ->
+  schemaProps = {}
+  schemaRequired = []
+
+  for {name, schema, required} in resolvedProps
+    schemaProps[name] = schema
+    schemaRequired.push name if required
+
+  schema =
+    type: 'object'
+    properties: schemaProps
+  schema.required = schemaRequired if schemaRequired.length > 0
+
+  cb null, schema
+
+
+handleObject = (objectType, simpleTypeSpec, options, cb) ->
+  async.waterfall [
+    (next) -> listProperties objectType, next
+    (properties, next) -> resolveProperties properties, options, next
+    (resolvedProps, next) -> buildObjectSchema resolvedProps, options, next
+  ], cb
+
+
+handleArray = (arrayType, simpleTypeSpec, options, cb) ->
+  cb null, type: 'array'
+
+
+handlePrimitiveType = (primitiveType, simpleTypeSpec, options, cb) ->
+  cb null, type: simpleTypeSpec.name
+
+
+handleType = (type, options, cb) ->
+  resolveType type, (err, simpleTypeSpec) ->
     return cb err if err
-    repr["$schema"] = "http://json-schema.org/draft-04/schema#"
-    cb null, repr
+    switch simpleTypeSpec.name
+      when 'object'
+        handleObject type, simpleTypeSpec, options, cb
+      when 'array'
+        handleArray type, simpleTypeSpec, options, cb
+      else
+        handlePrimitiveType type, simpleTypeSpec, options, cb
+
+
+addSchemaDeclaration = (schema, cb) ->
+  schema["$schema"] = "http://json-schema.org/draft-04/schema#"
+  cb null, schema
+
+
+transform = (ast, options, cb) ->
+  async.waterfall [
+    (next) -> handleType ast, options, next
+    addSchemaDeclaration
+  ], cb

 module.exports = {


Only pause ghc-modi stdout on command, and resume after

--- lib/ghc-modi-process-base.coffee
+++ lib/ghc-modi-process-base.coffee
@@ -34,7 +34,6 @@
       else
         modiPath = atom.config.get('haskell-ghc-mod.ghcModiPath')
         CP.spawn(modiPath, [], options)
-    proc.stdout.pause()
     proc.stderr.pause()
     proc.on 'exit', (code) =>
       debug "ghc-modi for #{rootDir.getPath()} ended with #{code}"
@@ -98,7 +97,7 @@
         callback []
       if text?
         debug "sending stdin text to #{modPath}"
-        child.stdin.write "#{text}\x04EOL"
+        child.stdin.write "#{text}\x04#{EOL}"
       handle()

   runModiCmd: ({dir, options, command, text, uri, args, callback, legacyInteractive}) =>
@@ -108,6 +107,10 @@
       debug "Failed. Falling back to ghc-mod"
       return @runModCmd {options, command, text, uri, args, callback}
     savedLines = []
+    process.stdout.pause()
+    callbackAndResume = (res) ->
+      process.stdout.resume()
+      callback res
     parseData = ->
       data = process.stdout.read()
       unless data?
@@ -116,7 +119,7 @@
           detail: dir.getPath()
           dismissable: true
         console.error savedLines
-        callback []
+        callbackAndResume []
         return
       data = data.toString()
       debug "Got response from ghc-modi:#{EOL}#{data}"
@@ -125,7 +128,7 @@
       result = lines[lines.length - 2]
       if result.match(/^OK/)
         lines = savedLines.slice(0, -2)
-        callback lines.map (line) ->
+        callbackAndResume lines.map (line) ->
           line.replace /\0/g, EOL
       else
         process.stdout.once 'readable', parseData
@@ -136,7 +139,7 @@
         data = process.stdout.read().toString()
         if data isnt "OK#{EOL}"
           debug "Failed to load file text for ghc-modi"
-          callback []
+          callbackAndResume []
           return
         debug "Successfully loaded file text for ghc-modi"
         process.stdout.once 'readable', parseData


Update import

Update import

--- bebop.coffee
+++ bebop.coffee
@@ -1,6 +1,6 @@
+exec = require 'executive'
 fs   = require 'fs'
 path = require 'path'
-exec = require('shortcake').exec

 writeFile = (dst, content) ->
   fs.writeFile dst, content, 'utf8', (err) ->


Fixes not snapping on drag-drop.

Fixes not snapping on drag-drop.

--- lib/multi-wrap-guide-view.coffee
+++ lib/multi-wrap-guide-view.coffee
@@ -218,6 +218,7 @@
     @columns = (parseInt(tip.textContent) for tip in @find 'div.multi-wrap-guide-tip')
     @saveColumns()
     @didChangeGuides()
+    @showGuides()

   # Private: Saves current columns to config if auto save enabled.
   saveColumns: ->


View: Use goog.object.forEach.

View: Use goog.object.forEach.

--- src/lib/core/View.coffee
+++ src/lib/core/View.coffee
@@ -292,7 +292,7 @@
     eventTypes = spark.core.View.EventTypes
     element    = @getElement()

-    for key, callback of @getOptions().eventHandlers
+    goog.object.forEach @getOptions().eventHandlers, (callback, key) =>
       if eventTypes[key] and typeof callback is 'function'
         @on goog.events.EventType[eventTypes[key]], callback


improve code for integration test

improve code for integration test

--- tests/server_module.integration.coffee
+++ tests/server_module.integration.coffee
@@ -151,23 +151,22 @@
     registrations: 'CustomerApp.CustomerRegistrations'

   @handle CustomerApp.RegisterCustomer, (command) ->
-
     registration = new CustomerApp.CustomerRegistration command.registrationId, command
     @repository.save registration

   @on CustomerApp.CustomerCreated, (event) ->
-
-    registrationId = @registrations.findOne(customerId: event.sourceId)._id
-    registration = @repository.find CustomerApp.CustomerRegistration, registrationId
+    registration = @_findRegistrationByCustomerId event.sourceId
     registration.onCustomerCreated event
-    @repository.save registration, registration.getVersion()
+    @repository.save registration

   @on CustomerApp.WelcomeEmailSent, (event) ->
-
-    registrationId = @registrations.findOne(customerId: event.customerId)._id
-    registration = @repository.find CustomerApp.CustomerRegistration, registrationId
+    registration = @_findRegistrationByCustomerId event.customerId
     registration.onWelcomeEmailSent()
     @repository.save registration
+
+  _findRegistrationByCustomerId: (customerId) ->
+    registrationId = @registrations.findOne(customerId: customerId)._id
+    return @repository.find CustomerApp.CustomerRegistration, registrationId

 class CustomerApp.CustomerRouter extends Space.messaging.Controller


remove bug in optional argument handling

--- libraries/metrics/mongodb.coffee
+++ libraries/metrics/mongodb.coffee
@@ -23,7 +23,6 @@
 				if (typeof callback == 'undefined')
 					callback = options
 					options = {}
-					callback = () ->

 				collection = db_command.collectionName
 				if collection.match(/\$cmd$/)
@@ -52,7 +51,6 @@
 				if (typeof callback == 'undefined')
 					callback = options
 					options = {}
-					callback = () ->

 				if ns.match(/\$cmd$/)
 					# Ignore noisy command methods like authenticating, ismaster and ping