Teambuilder: Improved move selection.

Teambuilder: Improved move selection.

--- client/app/js/views/teambuilder_view.coffee
+++ client/app/js/views/teambuilder_view.coffee
@@ -18,9 +18,9 @@
     'change .ev-entry': 'changeEv'
     'change .select-hidden-power': 'changeHiddenPower'
     'keyup .selected_moves input': 'keyupMoves'
-    'focus .selected_moves input': 'keyupMoves'
-    'click .table-moves tbody tr': 'selectMove'
-    'click .move-button': 'deselectMove'
+    'blur .selected_moves input': 'blurMoves'
+    'click .table-moves tbody tr': 'clickMoveName'
+    'click .move-button': 'clickSelectedMove'

   initialize: =>
     # TODO: Save these to something more global
@@ -130,8 +130,8 @@
     $table = @getActivePokemonView().find('.table-moves')
     $allMoves = $table.find('tbody tr')
     switch e.which
-      when 9, 13  # [Tab]/[Enter]; we're selecting the active move.
-        $activeMove = $allMoves.filter('.active').first()
+      when 13  # [Enter]; we're selecting the active move.
+        $activeMove = @$selectedMove()
         $activeMove.click()
       when 38  # [Up arrow]; selects move above
         $activeMove = $allMoves.filter('.active').first()
@@ -147,8 +147,7 @@
           $nextMove.addClass('active')
       else
         # Otherwise we're filtering moves
-        $this = $(e.currentTarget)
-        moveName = $this.val()
+        moveName = $input.val()
         @filterMovesBy(moveName)

   filterMovesBy: (moveName) =>
@@ -166,16 +165,24 @@
     $moves.first().addClass('active')
     $table.removeClass('hidden')

-  hideMoves: =>
-    $table = @getActivePokemonView().find('.table-moves')
-    $table.addClass('hidden')
-
-  selectMove: (e) =>
+  blurMoves: (e) =>
+    $input = $(e.currentTarget)
+    return  if $input.val().length == 0
+    moveName = @$selectedMove().data('move-id')
+    @insertMove($input, moveName)
+
+  clickMoveName: (e) =>
     $this = $(e.currentTarget)
     moveName = $this.data('move-id')
     $moves = @getActivePokemonView().find('.selected_moves')
-    $input = $moves.find('input').first()
-    @buttonify($input, moveName)
+    $input = $moves.find('input:focus').first()
+    $input ||= $moves.find('input').first()
+    return  if $input.length == 0
+    @insertMove($input, moveName)
+
+  insertMove: ($input, moveName) =>
+    return  if !@buttonify($input, moveName)
+    $moves = @getActivePokemonView().find('.selected_moves')
     $moves.find('input').first().focus()

     # Record moves
@@ -186,16 +193,22 @@
         movesArray.push(moveName)
     @getSelectedPokemon().set("moves", movesArray)

-  deselectMove: (e) =>
+  $selectedMove: =>
+    $table = @getActivePokemonView().find('.table-moves')
+    $allMoves = $table.find('tbody tr')
+    $allMoves.filter('.active').first()
+
+  clickSelectedMove: (e) =>
     $this = $(e.currentTarget)
     $input = $("<input type='text' value='#{$this.text()}'/>")
     $this.replaceWith($input)
     $input.focus()

   buttonify: ($input, moveName) =>
-    return  if moveName not of MoveData
+    return false  if moveName not of MoveData
     type = MoveData[moveName].type.toLowerCase()
     $input.replaceWith("""
#{moveName}
""")
+    return true

   setSelectedIndex: (index) =>
     pokemon = @collection.at(index)
@@ -250,6 +263,7 @@
       $this = $(el)
       moveName = $this.val()
       @buttonify($this, moveName)
+    this

   renderModal: =>
     if $('#import-team-modal').length == 0


make sure dash is imported

make sure dash is imported

--- client/app/Applications/Activity.kdapplication/AppController.coffee
+++ client/app/Applications/Activity.kdapplication/AppController.coffee
@@ -1,3 +1,5 @@
+{dash} = require 'bongo'
+
 class ActivityAppController extends AppController

   KD.registerAppClass this,


Workspace: Fixes for presence.

Workspace: Fixes for presence.

--- client/landing/CollaborativeWorkspace/collaborativeworkspace.coffee
+++ client/landing/CollaborativeWorkspace/collaborativeworkspace.coffee
@@ -81,7 +81,9 @@
       @usersRef.child(username).child("status").on "value", (snapshot) =>
         status = snapshot.val()
         if status is "online"
-          @activeUsers[username] = @users[username]  if @users[username]
+          @once "WorkspaceUsersFetched", =>
+            @activeUsers[username] = @users[username]
+
           @emit "SomeoneJoinedToTheSession", username
           @setPresenceHandler()  unless @presenceInterval
           if @amIHost()
@@ -89,7 +91,8 @@
             @addToHistory { message, by: KD.nick() }
         else if status is "offline"
           delete @activeUsers[username]
-          @killPresenceTimer()  if Object.keys(@activeUsers).length is 0
+          if Object.keys(@activeUsers).length is 0 and @amIHost()
+            @killPresenceTimer()
           @emit "SomeoneHasLeftTheSession", username
           if @amIHost()
             message = "#{username} has left the session"
@@ -345,6 +348,7 @@

   killPresenceTimer: ->
     @utils.killRepeat @presenceInterval
+    @presenceInterval = null

   showNotification: (message) ->
     return if @notification


Fix dk spec that would fail, even if dk worked

--- spec/operators-spec.coffee
+++ spec/operators-spec.coffee
@@ -316,7 +316,7 @@
         it "deletes the bottom two lines", ->
           keydown('d')
           keydown('k')
-          expect(editor.getText()).toBe("ABCDE")
+          expect(editor.getText()).toBe("12345")

       describe "on the beginning of the file", ->
         beforeEach ->
@@ -332,7 +332,7 @@
         it "deletes the first two lines", ->
           keydown('d')
           keydown('k')
-          expect(editor.getText()).toBe("12345")
+          expect(editor.getText()).toBe("ABCDE")

   describe "the D keybinding", ->


Move originalText declaration outside of before block

--- spec/operators-spec.coffee
+++ spec/operators-spec.coffee
@@ -396,9 +396,10 @@
         expect(editorElement.classList.contains('command-mode')).toBe(true)

     describe "when followed by an j", ->
-      beforeEach ->
-        @originalText = "12345\nabcde\nABCDE"
-        editor.setText(@originalText)
+      originalText = "12345\nabcde\nABCDE"
+
+      beforeEach ->
+        editor.setText(originalText)

       describe "on the beginning of the file", ->
         beforeEach ->
@@ -414,7 +415,7 @@
         it "deletes nothing", ->
           keydown('d')
           keydown('j')
-          expect(editor.getText()).toBe(@originalText)
+          expect(editor.getText()).toBe(originalText)

       describe "on the middle of second line", ->
         beforeEach ->
@@ -425,9 +426,10 @@
           expect(editor.getText()).toBe("12345")

     describe "when followed by an k", ->
-      beforeEach ->
-        @originalText = "12345\nabcde\nABCDE"
-        editor.setText(@originalText)
+      originalText = "12345\nabcde\nABCDE"
+
+      beforeEach ->
+        editor.setText(originalText)

       describe "on the end of the file", ->
         beforeEach ->
@@ -443,7 +445,7 @@
         it "deletes nothing", ->
           keydown('d')
           keydown('k')
-          expect(editor.getText()).toBe(@originalText)
+          expect(editor.getText()).toBe(originalText)

       describe "when on the middle of second line", ->
         beforeEach ->


Increase spec resiliency in tree-view-spec.coffee

Sometimes `treeView.currentlyOpening` doesn't contain the expected `Promise`,
thus causing failures like the one in https://circleci.com/gh/atom/atom/841.
It's possible that such `Promise` finishes before the test has the chance to
call `then` on it, thus causing it to be removed from that `Map` (see
https://github.com/atom/tree-view/blob/06bdfae/lib/tree-view.coffee#L223).

With this commit, we'll now simply wait until that `Promise` is removed from the
`currentlyOpening` `Map`, which should be more resilient.

--- spec/tree-view-spec.coffee
+++ spec/tree-view-spec.coffee
@@ -583,9 +583,10 @@
           sampleJs.trigger clickEvent(originalEvent: {detail: 2})
         .not.toThrow()

-        # Ensure we don't move on to the next test until the promise spawned click event resolves.
-        # (If it resolves in the middle of the next test we'll pollute that test)
-        waitsForPromise -> treeView.currentlyOpening.get(atom.workspace.getActivePaneItem().getPath())
+        waitsFor ->
+          # Ensure we don't move on to the next test until the promise spawned click event resolves.
+          # (If it resolves in the middle of the next test we'll pollute that test).
+          not treeView.currentlyOpening.has(atom.workspace.getActivePaneItem().getPath())

     describe "when the file is pending", ->
       editor = null


if edges being multiplied have no weights, use default weight of 1

--- src/algebra/algebra.coffee
+++ src/algebra/algebra.coffee
@@ -1,20 +1,25 @@
-{ log, p, similar } = require 'lightsaber'
+{ json, log, d, similar } = require 'lightsaber'
 { isFinite } = require 'lodash'

 Node = require '../core/node'
 Sphere = require '../core/sphere'

 class Algebra
+  @DEFAULT_WEIGHT = 1
+
   @multiply: (contentSphere, filterSphere) ->
     product = Sphere.copy contentSphere
     for __, contentEdge of contentSphere.edges
       for __, filterEdge of filterSphere.edges
-        if contentEdge.end.toJson().toLowerCase() is filterEdge.end.toJson().toLowerCase()
-          if isFinite(contentEdge.get('weight')) and isFinite(filterEdge.get('weight'))
-            product.addEdge
-              start: contentEdge.start
-              end: filterEdge.start
-              data: weight: contentEdge.get('weight') * filterEdge.get('weight')
+        if contentEdge.end.toJson(omit: 'id').toLowerCase() is filterEdge.end.toJson(omit: 'id').toLowerCase()
+          weight = if isFinite(contentEdge.get('weight')) and isFinite(filterEdge.get('weight'))
+            contentEdge.get('weight') * filterEdge.get('weight')
+          else
+            @DEFAULT_WEIGHT
+          product.addEdge
+            start: contentEdge.start
+            end: filterEdge.start
+            data: {weight}
     product

 module.exports = Algebra


Slider Component: Added event emits and fixed a bug about events

--- components/sliderbar/KDSliderBarView.coffee
+++ components/sliderbar/KDSliderBarView.coffee
@@ -8,7 +8,6 @@
     options.drawBar    ?= yes
     options.showLabels ?= yes
     options.snap       ?= yes
-    data.handles

     super options, data

@@ -21,8 +20,8 @@

     sortRef = (a,b) ->
       return -1 if a.getData().value < b.getData().value
-      return 1 if a.getData().value > b.getData().value
-      return 0
+      return  1 if a.getData().value > b.getData().value
+      return  0
     @handles.sort sortRef

   drawBar:->
@@ -82,15 +81,15 @@
     {maxValue, minValue, interval} = @getOptions()

     if @handles.length is 1
-      @handles.first.leftLimit     = minValue
-      @handles.first.rightLimit    = maxValue
+      @handles.first.data.leftLimit     = minValue
+      @handles.first.data.rightLimit    = maxValue
     else
       for handle in @handles
         i    = @handles.indexOf(handle)
         data = handle.data

         data.leftLimit  = @handles[i-1]?.getData().value + interval or minValue
-        data.rightLimit = @handles[i+1]?.getData().value - interval or maxValue
+        data.rightLimit = @handles[i+1]?.getData().value - interval or maxValue

   viewAppended:->
     @createHandles()
@@ -101,7 +100,7 @@
 class KDSliderBarHandleView extends KDCustomHTMLView
   constructor:(options = {}, data = {})->
     options.cssClass  = "handle"
-    options.name
+    options.name    or= "defaultHandle"
     data.value       ?= 0

     super options, data
@@ -113,18 +112,23 @@
     @on "mousedown", ->
       @parent.bindEvent "mousemove"
       windowController.setDragInAction true
+      @parent.emit "dragstart"

       @parent.on "mousemove", (event) =>
         relPos  = (((event.pageX - @parent.getX())/@parent.getWidth())*100)
         @setValue ((relPos*(maxValue - minValue))/100)+minValue

-      @on "mouseup", dragEnded
-      @parent.on "mouseleave", dragEnded
+        @parent.emit "draginaction"

-    dragEnded = ->
+      @on "mouseup", dragDidEnd
+      @parent.on "mouseleave", dragDidEnd
+
+    dragDidEnd =(e) ->
+      @off "mouseup"
       @parent.off "mousemove"
       windowController.setDragInAction false
       @snap() if @parent.getOption "snap"
+      @parent.emit "dragend"

   getPosition:->
     {maxValue, minValue} = @parent.getOptions()
@@ -147,6 +151,7 @@
     @parent.drawBar()
     @parent.setLimits()
     @setX "#{@getPosition()}%"
+    @parent.emit "valuechange"

   getSnappedValue:->
     {interval}  = @parent.getOptions()
@@ -173,4 +178,5 @@
   viewAppended:->
     @setX "#{@getPosition()}%"
     @attachEvents()
-    @snap() if @parent.getOption "snap"+    @snap() if @parent.getOption "snap"
+


Failed tests connection cleanup.

Failed tests connection cleanup.

--- test/testutils.coffee
+++ test/testutils.coffee
@@ -61,6 +61,16 @@
     redis.flushall()

+closeInstance = (service) ->
+  unless service then return
+  service.close()
+  .timeout 1500
+  .catch (e) ->
+    console.log 'Service closing error: ', e
+    service?.redis.disconnect()
+    service?.io.engine.close()
+  .catchReturn()
+
 cleanup = (services, sockets, done) ->
   services = _.castArray services
   sockets = _.castArray sockets
@@ -71,8 +81,7 @@
       Promise.fromCallback (cb) ->
         customCleanup cb
     else
-      Promise.each services, (service) ->
-        service?.close()
+      Promise.map services, closeInstance
   .finally ->
     customCleanup = null
     cleanDB()


throw banana after drawing gorillas

throw banana after drawing gorillas

--- views/gorillas.coffee
+++ views/gorillas.coffee
@@ -114,7 +114,6 @@
       building = @draw_building(position)
       position = building.end_position() + @padding

-
   draw_building:(x) ->
     building = new Building(@context)

@@ -131,14 +130,15 @@
     @color = color

   draw_gorillas: ->
-    building = @buildings[2]
-    gorilla_1 = new Gorilla(@context)
-    gorilla_1.draw(building.position_at_x(), building.position_at_y())
-    building = @buildings[7]
-    gorilla_2 = new Gorilla(@context)
-    gorilla_2.draw(building.position_at_x(), building.position_at_y())
-
-    gorilla_1.throw_banana()
+    building = @buildings[Math.floor(Math.random()*3)]
+    @player_1 = new Gorilla(@context)
+    @player_1.draw(building.position_at_x(), building.position_at_y())
+    building = @buildings[Math.floor(Math.random()*6)+3]
+    @player_2 = new Gorilla(@context)
+    @player_2.draw(building.position_at_x(), building.position_at_y())
+
+  throw_banana:(force, angle) ->
+    @player_1.throw_banana(force, angle)

 class Gorilla
   constructor:(@context) ->
@@ -154,9 +154,9 @@
     @y = y-40
     @context.drawImage(@image(), @x, @y, 40, 40)

-  throw_banana: ->
+  throw_banana:(force, angle) ->
     banana = new Banana(@context, @x+30, @y-30)
-    banana.throw(30, 45)
+    banana.throw(force, angle)

 class Banana
   constructor:(context, initial_x, initial_y) ->
@@ -170,8 +170,8 @@
   throw:(force, angle) ->
     @force = force
     @angle = angle
-
     @calculate_initial_position()
+
     setTimeout (=> @draw_frame()), 150

   draw: ->
@@ -205,3 +205,4 @@
   painter.draw_buildings()

   painter.draw_gorillas()
+  painter.throw_banana(30, 45)


fixed date range input if used date format option and input as numbe #67

--- _src/js/views/facets/daterange.coffee
+++ _src/js/views/facets/daterange.coffee
@@ -12,12 +12,19 @@
 				format: @model.get( "dateformat" )

 		if @model.get("value")?[0]?
-			_sd = moment( @model.get("value")[0], @model.get( "dateformat" ) )
+			@model.get("value")?[1]?
+			if _.isNumber( @model.get("value")[0] )
+				_sd = moment( @model.get("value")[0] )
+			else
+				_sd = moment( @model.get("value")[0], @model.get( "dateformat" ) )
 			if _sd.isValid()
 				_opts.startDate = _sd._d

 		if @model.get("value")?[1]?
-			_ed = moment( @model.get("value")[1], @model.get( "dateformat" ) )
+			if _.isNumber( @model.get("value")[1] )
+				_ed = moment( @model.get("value")[1] )
+			else
+				_ed = moment( @model.get("value")[1], @model.get( "dateformat" ) )
 			if _ed.isValid()
 				_opts.endDate = _ed._d
 		return _opts


select2 facet is now editable. closes #40

--- _src/js/views/facets/subselect.coffee
+++ _src/js/views/facets/subselect.coffee
@@ -34,12 +34,30 @@
 			#@$inp.select2( "open" )
 		return super

+	_isFull: =>
+		if @selectCount <= 0
+			return false
+		return ( @result or []).length >= @selectCount
+
 	reopen: ( pView )=>
-		return
+		if @_isFull()
+			return
+		# reset results and select2
+		pView.$results.empty()
+		@select2.$container.off()
+		@select2.destroy()
+		@result.reset()
+		@select2 = null
+
+		# set the current values
+		_oldVals = @result.pluck( "value" )
+		@model.set( value: _oldVals )
+
+		return super

 	_initSelect2: =>
 		if not @select2?
-			_opts = _.extend( {}, @defaultModuleOpts, @model.get( "opts" ), { multiple: @model.get( "multiple" ) }, @forcedModuleOpts )
+			_opts = _.extend( {}, @defaultModuleOpts, @model.get( "opts" ), { multiple: @model.get( "multiple" ) or false }, @forcedModuleOpts )
 			@$inp.select2( _opts )
 			@select2 = @$inp.data( "select2" )
 			if not @model.get( "multiple" )
@@ -47,7 +65,7 @@

 			# after loading try to set the cursor focus
 			@select2.on "results:all", =>
-				@select2.selection.$search.focus()
+				@select2.selection?.$search?.focus?()
 				return

 			# listen to async result changes and set the selection


Some cleanup to masonry setup.

Some cleanup to masonry setup.

--- app/assets/javascripts/albums.js.coffee
+++ app/assets/javascripts/albums.js.coffee
@@ -6,27 +6,23 @@
     gutter: 15

   prepended: =>
-    currentBatch = @queue.splice(0, @queue.length)
-    items = @processImgs(currentBatch)
+    items = @processImgs(@queue.splice(0, @queue.length))
     @masonry.reloadItems()
     @masonry.prepended(items)
+    @masonry.reloadItems()

   processImgs: (imgs) ->
-    items = []
-    _(imgs).each (img) =>
+    _(imgs).map (img) =>
       $item = @$itemFromImg(img)
       $item.addClass('is-shown')
-      items.push($item[0])
-    items
+      $item[0]

   $itemFromImg: (img) ->
     $(img).parents('.grid-item')

   constructor: (args) ->
     @queue = []
-    @completed = []
-    @container = args.container
-    @$container = $(@container).masonry(
+    @$container = $(args.container).masonry(
       _(args.options || {}).defaults(@defaultOptions)
     )
     @masonry = @$container.data('masonry')


minor changes to ensure this feature works

--- app/assets/javascripts/groups.js.coffee
+++ app/assets/javascripts/groups.js.coffee
@@ -1,3 +1,4 @@
+#*** HTML5 SUPPORT ***
 canvasSupported = !!window.HTMLCanvasElement
 html5 = exports ? this
 html5.supported = true if canvasSupported
@@ -93,26 +94,19 @@
     )
 # discussions

-getNextURL = (button_url) ->
-  console.log("0" + button_url)
+getNextURL = (button_url) ->
   current = document.URL
+  newURL = current
   lastBackslash = current.lastIndexOf("/")
-
   #checks if main url has backslash in the way
   if lastBackslash == current.length-1
     current = current.substr(0, lastBackslash)
-
   #if current is already on a page number remove current page number
   if current.lastIndexOf("?") > -1
     newURL = current.split("?")[0]
-    console.log("1" + newURL)
-
   if button_url.lastIndexOf("?") > -1
-  	console.log("2" + button_url)
   	page_number = button_url.split("?")
-  	newURL = current + "?" + page_number[1]
-
-  console.log("3 changing:" + button_url + " to:" + newURL)
+  	newURL = newURL + "?" + page_number[1]
   newURL

 getPageParam = () ->
@@ -133,13 +127,13 @@
       $("#group-discussions").removeClass('hidden')
       $("#discussions-loading").hide()
     )
+
 $ ->
   if $("body.groups.show").length > 0
     $(document).on('click', '#group-discussions .pagination a', (e)->
       unless $(this).parent().hasClass("gap")
         if html5.supported
           window.history.pushState("stateObj", "title_ignored", getNextURL($(this).attr("href")))
-          console.log("html5 supported")
         $("#discussion-list").hide()
         $("#discussions-loading").show()
         $('#group-discussions').load($(this).attr('href'), ->


Use select2 for bulk issue edit status

Use select2 for bulk issue edit status

--- app/assets/javascripts/issues.js.coffee
+++ app/assets/javascripts/issues.js.coffee
@@ -29,7 +29,7 @@
     $('#filter_issue_search').val($('#issue_search').val())

   initSelects: ->
-    $("select#update_status").select2(width: 'resolve', dropdownAutoWidth: true)
+    $("select#update_state_event").select2(width: 'resolve', dropdownAutoWidth: true)
     $("select#update_assignee_id").select2(width: 'resolve', dropdownAutoWidth: true)
     $("select#update_milestone_id").select2(width: 'resolve', dropdownAutoWidth: true)
     $("select#label_name").select2(width: 'resolve', dropdownAutoWidth: true)


initial pass at new CSV parser

initial pass at new CSV parser

--- app/classifier/tasks/survey/editor.cjsx
+++ app/classifier/tasks/survey/editor.cjsx
@@ -33,7 +33,7 @@

Import task data

-        <FileButton className="major-button column" accept=".csv, .tsv" multiple onSelect={@handleFiles.bind this, @addChoice}>Add choices CSV</FileButton>
+        <FileButton className="major-button column" accept=".csv, .tsv" multiple onSelect={@handleFiles.bind this, @addChoice, null}>Add choices CSV</FileButton>
         <TriggeredModalForm trigger={

@@ -43,7 +43,7 @@
         </TriggeredModalForm>

-        <FileButton className="major-button column" accept=".csv, .tsv" multiple onSelect={@handleFiles.bind this, @addCharacteristics}>Add characteristics CSV</FileButton>
+        <FileButton className="major-button column" accept=".csv, .tsv" multiple onSelect={@handleFiles.bind this, @addCharacteristics, null}>Add characteristics CSV</FileButton>
         <TriggeredModalForm trigger={

@@ -53,7 +53,7 @@
         </TriggeredModalForm>

-        <FileButton className="major-button column" accept=".csv, .tsv" multiple onSelect={@handleFiles.bind this, @addConfusion}>Add confused pairs CSV</FileButton>
+        <FileButton className="major-button column" accept=".csv, .tsv" multiple onSelect={@handleFiles.bind this, @addConfusion, null}>Add confused pairs CSV</FileButton>
         <TriggeredModalForm trigger={

@@ -63,7 +63,7 @@
         </TriggeredModalForm>

-        <FileButton className="major-button column" multiple onSelect={@handleFiles.bind this, @addQuestion}>Add questions CSV</FileButton>
+        <FileButton className="major-button column" multiple onSelect={@handleFiles.bind this, @addQuestion, @cleanQuestions}>Add questions CSV</FileButton>
         <TriggeredModalForm trigger={

@@ -185,7 +185,7 @@

-  handleFiles: (forEachRow, e) ->
+  handleFiles: (forEachRow, afterFileHook, e) ->
     @setState
       importErrors: []
     Array::slice.call(e.target.files).forEach (file) =>
@@ -201,6 +201,7 @@
           throw error
           @handleImportError error, file
         .then =>
+          afterFileHook?()
           @props.workflow.update('tasks').save()

   readFile: (file) ->
@@ -308,36 +309,32 @@
     @props.task.choices[choiceID]?.confusionsOrder.push confusionID
     @props.task.choices[choiceID]?.confusions[confusionID] = details

-  addQuestion: ({question, multiple, required, answers, name, __parsedExtra}) ->
-    unless question? or name?
+  addQuestion: ({question, multiple, required, answers, include, exclude, __parsedExtra}) ->
+    unless question?
       throw new Error 'Questions require a "question" column'
     unless answers?
       throw new Error 'Questions require a "answers" column'

-    choices = null
-    questionID = null
-    if(!!name)
-      choices = name.split(/\s*;s*/)
-    if(!!question)
-      questionID = @makeID question
+    includeChoices = include?.split /\s*;\s*/
+    excludeChoices = exclude?.split /\s*;\s*/
+    questionID = @makeID question

     # don't put it in the default questionsOrder if we've specified choices it should map to
-    unless choices? or questionID in @props.task.questionsOrder
-      @props.task.questionsOrder.push questionID
+    unless include? or questionID in @props.task.questionsOrder
+      (maybeProp @props.task.questionsOrder, []).push questionID

     # if we specified mapped choices, create those entries instead
-    if choices?
-      @props.task.questionsMap ?= {}
-      for choice in choices
-        continue unless !!choice
+    if includeChoices?
+      for choice in includeChoices
+        continue unless choice?
         choiceID = @makeID choice
-        @props.task.questionsMap[choiceID] ?= []
-        @props.task.questionsMap[choiceID].push questionID if questionID?
-
-    # we only can get here if the question stuff is empty and the names are not empty
-    # that means there will be no questions for this choice, so we don't need to actually
-    # store any questions or answers for this row of the csv
-    return unless questionID?
+        (maybeProp @props.task, inclusions, []).push [choiceID, questionID]
+
+    if excludeChoices?
+      for choice in excludeChoices
+        continue unless choice?
+        choiceID = @makeID choice
+        (maybeProp @props.task, exclusions, []). push [choiceID, questionID]

     @props.task.questions[questionID] =
       label: question
@@ -346,11 +343,32 @@
       answersOrder: []
       answers: {}

-    for answer in answers.split(/;/).concat(__parsedExtra ? []).filter Boolean
+    for answer in ((answers.split /;/).concat __parsedExtra ? []).filter Boolean
       answerID = @makeID answer
       @props.task.questions[questionID].answersOrder.push answerID
       @props.task.questions[questionID].answers[answerID] =
         label: answer
+
+  maybeProp: (owner, prop, defVal) ->
+    owner[prop] = defVal unless owner[prop]?
+    owner[prop]
+
+  cleanQuestions: () ->
+    if @props.task.includeChoices?
+      for includeTuple in @props.task.includeChoices
+        (maybeProp (maybeProp @props.task.questionsMap, {}) includeTuple[0], []).push includeTuple[1]
+
+    if @props.task.questionsOrder?
+      for questionID in @props.task.questionsOrder
+        for choiceID in @props.task.choicesOrder
+          (maybeProp (maybeProp @props.task.questionsMap, {}) choiceID, []).push questionID
+
+    if @props.task.excludeChoices?
+      for excludeTuple in @props.task.excludeChoices
+        questionList = @props.task.questionsMap[excludeTuple[0]]
+        questionList.splice (questionList.indexOf excludeTuple[1]) 1
+
+    return

   handleImageAdd: (media) ->
     @props.task.images[media.metadata.filename] = media.src


Improving watchers

Improving watchers

--- app/coffee/modules/issues/detail.coffee
+++ app/coffee/modules/issues/detail.coffee
@@ -234,6 +234,13 @@
             watchers = _.map(watcherIds, (watcherId) -> $scope.usersById[watcherId])
             html = template({watchers: watchers, editable:editable})
             $el.html(html)
+            console.log "--------", watchers, watchers.length
+            if watchers.length == 0
+                if editable
+                    $el.find(".title").text("Add watchers")
+                    $el.find(".watchers-header").addClass("no-watchers")
+                else
+                    $el.find(".watchers-header").hide()

         if not editable
             $el.find(".add-watcher").remove()


Redirection en relatif

Redirection en relatif

--- app/scripts/services/UserService.coffee
+++ app/scripts/services/UserService.coffee
@@ -29,7 +29,7 @@
       , 10000

     goLogin: ->
-      window.location.replace("/#/login/#{LieuService.getLieu().value}")
+      window.location.replace("#/login/#{LieuService.getLieu().value}")

     signIn: (login, password) ->
       @promise = $q (resolve, reject) =>


Migrate API docs from github wiki to source (based on dgeni)

--- src/bounded_context.coffee
+++ src/bounded_context.coffee
@@ -20,6 +20,22 @@
     @_readModelInstances = {}

+
+  ###*
+  * @name initialize
+  *
+  * @description
+  * Use as: initialize(callback)
+  *
+  * Initializes the `BoundedContext` after the `add*` Methods
+  *
+  * @example
+    ```javascript
+    exampleContext.initialize(function() {
+      // ...
+    })
+    ```
+  ###
   initialize: ->
     @_initializeStore()
     @_initializeReadModels()
@@ -101,6 +117,21 @@
       @_aggregateService.registerAggregateRoot aggregateName, AggregateRoot

+  ###*
+  * @name set
+  *
+  * @description
+  * > Use as: set(key, value)
+  * Configure settings for the `BoundedContext`.
+  *
+  * @example
+
+     exampleContext.set 'store', StoreAdapter
+
+  *
+  * @param {Object} key
+  * Available keys are: `store` Eventric Store Adapter
+  ###
   set: (key, value) ->
     @_params[key] = value
     @
@@ -116,6 +147,34 @@
     @

+  ###*
+  * @name addCommandHandler
+  *
+  * @dscription
+  * Use as: addCommandHandler(commandName, commandFunction)
+  *
+  * Add Commands to the `BoundedContext`. These will be available to the `command` method after calling `initialize`.
+  *
+  * @example
+    ```javascript
+    exampleContext.addCommandHandler('someCommand', function(params, callback) {
+      // ...
+    });
+    ```
+
+  * @param {String} commandName Name of the command
+  *
+  * @param {String} commandFunction Gets `this.aggregate` dependency injected
+  * `this.aggregate.command(params)` Execute command on Aggregate
+  *  * `params.name` Name of the Aggregate
+  *  * `params.id` Id of the Aggregate
+  *  * `params.methodName` MethodName inside the Aggregate
+  *  * `params.methodParams` Array of params which the specified AggregateMethod will get as function signature using a [splat](http://stackoverflow.com/questions/6201657/what-does-splats-mean-in-the-coffeescript-tutorial)
+  *
+  * `this.aggregate.create(params)` Execute command on Aggregate
+  *  * `params.name` Name of the Aggregate to be created
+  *  * `params.props` Initial properties so be set on the Aggregate or handed to the Aggregates create() method
+  ###
   addCommandHandler: (commandHandlerName, commandHandlerFn) ->
     @_commandHandlers[commandHandlerName] = => commandHandlerFn.apply @_di, arguments
     @
@@ -126,11 +185,60 @@
     @

+  ###*
+  * @name addAggregate
+  *
+  * @description
+  *
+  * Use as: addAggregate(aggregateName, aggregateDefinition)
+  *
+  * Add [Aggregates](https://github.com/efacilitation/eventric/wiki/BuildingBlocks#aggregateroot) to the `BoundedContext`. It takes an AggregateDefinition as argument. The AggregateDefinition must at least consists of one AggregateRoot and can optionally have multiple named AggregateEntities. The Root and Entities itself are completely vanilla since eventric follows the philosophy that your DomainModel-Code should be technology-agnostic.
+  *
+  * @example
+
+  ```javascript
+  exampleContext.addAggregate('Example', {
+    root: function(){
+      this.doSomething = function(description) {
+        // ...
+      }
+    },
+    entities: {
+      'ExampleEntityOne': function() {},
+      'ExampleEntityTwo': function() {}
+    }
+  });
+  ```
+  *
+  * @param {String} aggregateName Name of the Aggregate
+  * @param {String} aggregateDefinition Definition containing root and entities
+  ###
   addAggregate: (aggregateName, AggregateRootClass) ->
     @_aggregateRootClasses[aggregateName] = AggregateRootClass
     @

+  ###*
+  *
+  * @name addDomainEventHandler
+  *
+  * @description
+  * Use as: addDomainEventHandler(domainEventName, domainEventHandlerFunction)
+  *
+  * Add handler function which gets called when a specific `DomainEvent` gets triggered
+  *
+  * @example
+    ```javascript
+    exampleContext.addDomainEventHandler('Example:create', function(domainEvent) {
+      // ...
+    });
+    ```
+  *
+  * @param {String} domainEventName Name of the `DomainEvent`
+  *
+  * @param {Function} Function which gets called with `domainEvent` as argument
+  * - `domainEvent` Instance of [[DomainEvent]]
+  ###
   addDomainEventHandler: (eventName, handlerFn) ->
     @_domainEventHandlers[eventName] = [] unless @_domainEventHandlers[eventName]
     @_domainEventHandlers[eventName].push => handlerFn.apply @_di, arguments
@@ -174,6 +282,32 @@
     @_domainEventClasses[domainEventName]

+  ###*
+  * @name command
+  *
+  * @description
+  * Use as: command(command, callback)
+  *
+  * Execute previously added `commands`. Upon successful execution this will trigger a `DomainEvent`.
+  *
+  * @example
+    ```javascript
+    exampleContext.command({
+      name: 'doSomething'
+    },
+    function(err, result) {
+      // callback
+    });
+    ```
+  *
+  * @param {Object} command Object containing the command definition
+  * - `name` The name of the `command`
+  * - `params` Object containing parameters. The `command` will get this as first parameter.
+  *
+  * @param {Function} callback Gets called after the command got executed with the arguments:
+  * - `err` null if successful
+  * - `result` Set by the `command`
+  ###
   command: (command, callback) ->
     new Promise (resolve, reject) =>
       if @_commandHandlers[command.name]


JSView update

JSView update

--- JSKit/02_JSView.coffee
+++ JSKit/02_JSView.coffee
@@ -379,4 +379,12 @@
 						e = event
 					@touchesEnded(e)
 			'touchcancel':=>
-				alert('cancel')
+				if (@_editable == false)
+					event.preventDefault()
+				@touched = false
+				if (typeof @touchesCancelled == 'function')
+					if (isTouch)
+						e = event.changedTouches[0]
+					else
+						e = event
+					@touchesCancelled(e)


Adds isTypeOf

Adds isTypeOf

--- api/apps/articles/model/schema.coffee
+++ api/apps/articles/model/schema.coffee
@@ -6,14 +6,20 @@
 # Input Schema
 #
 imageSection = (->
-  @object().meta(name: 'Image').keys
+  @object().meta(
+    name: 'Image'
+    isTypeOf: (data) => data.type is 'image'
+  ).keys
     type: @string().valid('image')
     url: @string().allow('', null)
     caption: @string().allow('', null)
 ).call Joi

 videoSection = (->
-  @object().meta(name: 'Video').keys
+  @object().meta(
+    name: 'Video'
+    isTypeOf: (data) => data.type is 'video'
+  ).keys
     type: @string().valid('video')
     url: @string().allow('', null)
     cover_image_url: @string().allow('', null)
@@ -22,7 +28,10 @@
 ).call Joi

 fullscreenSection = (->
-  @object().meta(name: 'Fullscreen').keys
+  @object().meta(
+    name: 'Fullscreen'
+    isTypeOf: (data) => data.type is 'fullscreen'
+  ).keys
     type: @string().valid('fullscreen')
     title: @string().allow('',null)
     intro: @string().allow('',null)


fixed dropdown on courses view

fixed dropdown on courses view

--- app/assets/javascripts/courses.coffee
+++ app/assets/javascripts/courses.coffee
@@ -4,20 +4,23 @@
 $ ->
 	#Listener on the dropdown menu
 	$("#42").change ->
-		$('.file').css("display", "block")
+		$('.Jedes').css("display", "block")
+		$('.Nicht').css("display", "block")
 		filter = $("#42").val()
 		semester = filter.split(' ')[0]
 		year = parseInt(filter.split(' ')[1].substring(0,4))
 		# hide all events with irregular cycle
-		$('.Nicht').hide()
+
 		# hide all events that does not match the selected cycle
 		if filter.indexOf('Sommersemester') != -1
+			$('.Nicht').hide()
 			$('.Jedes.Wintersemester').hide()
 			if year % 2 == 1
 				$('.gerade').hide()
 			else
 				$('.ungerade').hide()
 		if filter.indexOf('Wintersemester') != -1
+			$('.Nicht').hide()
 			$('.Jedes.Sommersemester').hide()
 			if year % 2 == 1
 				$('.gerade').hide()


Update ignore tag names list

Update ignore tag names list

--- app/assets/javascripts/home.js.coffee
+++ app/assets/javascripts/home.js.coffee
@@ -91,7 +91,9 @@
     console.log("#{spamCount} / #{data.length}") if isDev
     $('#is-pic-list-last-item').appendTo('#js-pic-list').show()

-  ignoreTagNames = [ 'support', 'skin', 'random', 'shadow', 'fashion', 'perfect' ]
+  ignoreTagNames = [
+    'support', 'skin', 'random', 'shadow', 'fashion', 'perfect', 'crazy'
+  ]
   tagCountThreshold = 30
   spamFilter = (d)->
     _isSpam = false


minor change

minor change

--- app/assets/javascripts/lessons.coffee
+++ app/assets/javascripts/lessons.coffee
@@ -79,7 +79,7 @@
     while a < numHead
       thisA = $(thisHead).get(a)
       aText = $(thisA).text()
-      if headingClass == 'Lesson'
+      if aText.indexOf(',') != -1
         y = aText.indexOf(',') + 2
         aText = aText.substring(y, aText.length)
         aTextFinal = toTitleCase(aText)


Fix regression thanks to tests : no result message is not cleared is subsequent search have results.

--- app/assets/javascripts/suggest.coffee
+++ app/assets/javascripts/suggest.coffee
@@ -48,6 +48,7 @@

   displayPreviewResult : (data) ->
      $('#previewEvents').empty()
+     $( "#callbackNoResult" ).hide()

      console.log data


:bug:Fix admin dashboard invoice sorting

Bug was introduced by recent perf work.

--- server/payments/subscription_handler.coffee
+++ server/payments/subscription_handler.coffee
@@ -177,7 +177,9 @@
     processInvoices null, (err) =>
       return @sendDatabaseError(res, err) if err
       subMap = {}
-      for invoiceID, invoice of @invoices
+      invoices = (invoice for invoiceID, invoice of @invoices)
+      invoices.sort (a, b) -> if a.date > b.date then -1 else 1
+      for invoice in invoices
         subID = invoice.subscriptionID
         if subID of subMap
           subMap[subID].first = invoice.date


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/engine_selector.coffee
+++ src/engine_selector.coffee
@@ -22,7 +22,7 @@
   ###
   closest: (selector, position) ->
     self.find(selector).sort (a, b) ->
-      Point.distanceSquared(position, a.position()) - Point.d
+      Point.distanceSquared(position, a.position()) - Point.distanceSquared()

   ###*
   Get a selection of GameObjects that match the specified selector criteria. The selector language


whitespace change for consistency with old

--- app/controllers/subject_viewer.coffee
+++ app/controllers/subject_viewer.coffee
@@ -72,10 +72,11 @@
       if ExperimentServer.currentCohort?
         @classification.metadata.cohort = ExperimentServer.currentCohort
       @html template @classification
+
       @active = Math.floor @classification.subject.location.standard.length / 2
       @activate @active
+
       @onClassificationChange()
-
     else
       @html ''


Acknowledgements Page

Acknowledgements Page

--- app/pages/about/acknowledgements.cjsx
+++ app/pages/about/acknowledgements.cjsx
@@ -5,19 +5,16 @@
 counterpart.registerTranslations 'en',
   acknowledgements:
    pageContent: '''
-    ## How to Acknowledge the Zooniverse in Papers
+    ## Acknowledging the Zooniverse
+    Per the [Zooniverse Project Builder Policies](/lab-policies), all research publications using any data derived from Zooniverse approved projects (those listed on the [Zooniverse Projects page](/projects)) are required to acknowledge the Zooniverse and the Project Builder platform. To do so, please use the following text:

-    If you’ve collected research from a Zooniverse project, we request that you acknowledge Zooniverse in any publications. To do so, please use the following text:
+    *This publication uses data generated via the [Zooniverse.org](https://www.zooniverse.org/) platform, development of which was supported by a Global Impact Award from Google, and by the Alfred P. Sloan Foundation.*

-    *This publication uses data generated via the Zooniverse.org platform, development of which was supported by a Global Impact Award from Google, and by the Alfred P. Sloan Foundation.*
+    We ask that all researchers making use of the Zooniverse Project Builder platform in any way also consider including the above acknowledgement in their publications.

-    You can find a list of publications written using the Zooniverse on the Publications tab (link).
+    We strongly encourage project owners to report published accepted research publications using Zooniverse-produced data to us via [this form](https://docs.google.com/forms/d/e/1FAIpQLSdbAKVT2tGs1WfBqWNrMekFE5lL4ZuMnWlwJuCuNM33QO2ZYg/viewform). You can find a list of publications written using the Zooniverse on our [Publications page](publications).

-    If you would like to reference the project builder, information for this is below:
-
-    *Bowyer, A.; Lintott, C.; Hines, G.; Allen, C.; Paget, E. Panoptes, a project building tool for citizen science. 2015; in press. [Google Scholar](url)*
-
-    If you have any questions about how to acknowledge the Zooniverse, such as referencing a particular individual or custom code, get in touch (link).
+    If you have any questions about how to acknowledge the Zooniverse, such as referencing a particular individual or custom code, please [get in touch](contact).
 '''

 module.exports = React.createClass


Handle broken symlinks when walking

Handle broken symlinks when walking

--- build/tasks/output-module-counts.coffee
+++ build/tasks/output-module-counts.coffee
@@ -1,3 +1,4 @@
+fs = require 'fs'
 path = require 'path'

 module.exports = (grunt) ->
@@ -19,7 +20,7 @@
     getOtherTotal = ->
       Object.keys(otherModules).length

-    grunt.file.recurse nodeModulesDir, (absolutePath, rootPath, relativePath, fileName) ->
+    recurseHandler = (absolutePath, rootPath, relativePath, fileName) ->
       return if fileName isnt 'package.json'

       {name, version, repository} = grunt.file.readJSON(absolutePath)
@@ -36,6 +37,22 @@
       modules[name].count++
       modules[name].versions[version] = true

+    walkNodeModuleDir = ->
+      grunt.file.recurse(nodeModulesDir, recurseHandler)
+
+    # Handle broken symlinks that grunt.file.recurse fails to handle
+    loop
+      try
+        walkNodeModuleDir()
+        break
+      catch error
+        if error.code is 'ENOENT'
+          fs.unlinkSync(error.path)
+          otherModules = {}
+          atomModules = {}
+        else
+          break
+
     if getAtomTotal() > 0
       console.log "Atom Modules: #{getAtomTotal()}"
       sortModuleNames(atomModules).forEach (name) ->


Fixed:  is immutable and attributes should be retrieved through

--- client/app/components/email-list.coffee
+++ client/app/components/email-list.coffee
@@ -26,7 +26,7 @@
                         # only displays initial email of a thread
                         if email.get('inReplyTo').length is 0
                             isActive = @props.openEmail? and
-                                       @props.openEmail.id is email.get('id')
+                                       @props.openEmail.get('id') is email.get('id')
                             @getEmailRender email, key, isActive
                     .toJS()


client: welcome: minor

client: welcome: minor

--- client/app/lib/util/routeHandler.coffee
+++ client/app/lib/util/routeHandler.coffee
@@ -38,8 +38,7 @@
         router.handleRoute path
       router.handleRoute '/IDE'
     else
-      groupsController.ready ->
-        new WelcomeModal
+      groupsController.ready -> new WelcomeModal

   lazyrouter.bind name, (type, info, state, path, ctx) ->


Adds cmd+enter shortcut to save an entry.

--- client/source/views/entries/edit.coffee
+++ client/source/views/entries/edit.coffee
@@ -26,6 +26,19 @@
     'click [href="#publish"]': 'clickPublish'
     'click [href="#copy"]': 'clickCopy'
     'click [href="#reject"]': 'clickReject'
+    'keydown textarea, [type=text], [type=number]': 'keyDown'
+    'keyup textarea, [type=text], [type=number]': 'keyUp'
+
+  keyUp: (e) ->
+    if @cmdActive and e.which is 91
+      @cmdActive = false
+      e
+
+  keyDown: (e) ->
+    if @cmdActive and e.which is 13
+      @$('form').submit()
+    @cmdActive = e.metaKey
+    e

   getTemplateData: ->
     fields = @bucket.get('fields')


Added New page creation animation

Added New page creation animation

This is a fix for
https://openedx.atlassian.net/browse/TNL-136
added scroll to new page and remove class 'new' on hover to better
indicate that a new page is created.

--- cms/static/coffee/src/views/tabs.coffee
+++ cms/static/coffee/src/views/tabs.coffee
@@ -89,7 +89,9 @@
       editor.$el.addClass('new')
       setTimeout(=>
         editor.$el.removeClass('new')
-      , 500)
+      , 1000)
+      $('html, body').animate {scrollTop: $('.new-component-item').offset().top}, 500
+

       editor.createItem(
         @model.get('id'),


dont serialize entire req object when displaying graphiql

--- src/lib/metaphysics.coffee
+++ src/lib/metaphysics.coffee
@@ -43,8 +43,7 @@

 metaphysics.debug = (req, res, send) ->
   if req.query.query?
-    get = extend {}, send,
-      variables: JSON.stringify send.variables
+    get = { query: send.query, variables: JSON.stringify send.variables }

     res.redirect "#{METAPHYSICS_ENDPOINT}?#{qs.stringify get}"


cleanup template creation

cleanup template creation

--- lib/assets/javascripts/stark/view.js.coffee
+++ lib/assets/javascripts/stark/view.js.coffee
@@ -128,13 +128,18 @@
   # In practice, this can be overidden to use your preferred
   # template library, as long as it responds to #render(context),
   # where context is a reference to the view itself.
-  create_template: ->
-    new Template(@jst())
+  #
+  # @param [String] src       Contents of the template
+  #
+  # @return [Template]
+  create_template: (src) ->
+    new Template(src)

   # Render the configured @template to HTML
   render_html: ->
-    return "" if not @template?
-    @create_template().render(@)
+    tpl = @jst()
+    return "" if not @template? or not tpl
+    @create_template(tpl).render(@)

   # Get or set the view's html.
   #
@@ -269,7 +274,7 @@
   # @param [Object] data    Optional data to include in context
   include: (tpl, data) ->
     data ||= {}
-    return new Template(@jst(tpl)).render( _.extend({}, @, data) )
+    return @create_template(@jst(tpl)).render( _.extend({}, @, data) )

   # Render a partial (sub) view into the given selector
   #


update has many through

update has many through

--- lib/client/javascripts/nali/model.js.coffee
+++ lib/client/javascripts/nali/model.js.coffee
@@ -314,7 +314,7 @@
       @[ name ] = @Collection.new model, ->
         return true for model in @[ through ] when model[ key ] is @
         false
-      @[ name ].add model[ key ] for model in @[ through ]
+      @[ name ].add @[ through ].pluck key
       @[ name ].subscribeTo @[ through ], 'update.length.add',    ( collection, model ) -> @add    model[ key ]
       @[ name ].subscribeTo @[ through ], 'update.length.remove', ( collection, model ) -> @remove model[ key ]
       @[ name ]


Visual/edit modes: more changes...

Visual/edit modes: more changes...

- Simplify the SuppressPrintable key handler.
- Rework the protectClipboard logic.
- Some commands (x, X, etc) never affect the clipboard.

--- content_scripts/mode_visual_edit.coffee
+++ content_scripts/mode_visual_edit.coffee
@@ -13,18 +13,13 @@
 class SuppressPrintable extends Mode
   constructor: (options = {}) ->
     handler = (event) =>
-      if KeyboardUtils.isPrintable event
-        if event.type == "keydown"
-          # Completely suppress Backspace and Delete, they change the selection.
-          if event.keyCode in [ 8, 46 ]
-            @suppressEvent
-          else
-            DomUtils.suppressPropagation event
-            @stopBubblingAndFalse
-        else
-          @suppressEvent
-      else
-        @stopBubblingAndTrue
+      return @stopBubblingAndTrue if not KeyboardUtils.isPrintable event
+      return @suppressEvent if event.type != "keydown"
+      # Completely suppress Backspace and Delete, they change the selection.
+      @suppressEvent if event.keyCode in [ 8, 46 ]
+      # Suppress propagation (but not preventDefault) for keydown, printable events.
+      DomUtils.suppressPropagation event
+      @stopBubblingAndFalse

     super extend options,
       keydown: handler
@@ -67,11 +62,28 @@
 class Movement extends CountPrefix
   opposite: forward: backward, backward: forward

-  copy: (text) ->
-    chrome.runtime.sendMessage handler: "copyToClipboard", data: text if text
-
   paste: (callback) ->
     chrome.runtime.sendMessage handler: "pasteFromClipboard", (response) -> callback response
+
+  copy: (text, isFinalUserCopy = false) ->
+    chrome.runtime.sendMessage handler: "copyToClipboard", data: text
+    # If isFinalUserCopy is set, then we're copying the final text selected by the user (and exiting).
+    # However, we may be called from within @protectClipboard, which will later try to restore the clipboard's
+    # contents.  Therefore, we disable copy so that subsequent calls will not be propagated.
+    @copy = (->) if isFinalUserCopy
+
+  # This used whenever manipulating the selection may, as a side effect, change the clipboard's contents.  We
+  # restore the original clipboard contents when we're done. May be asynchronous.  We use a lock so that calls
+  # can be nested.
+  protectClipboard: do ->
+    locked = false
+
+    (func) ->
+      if locked then func()
+      else
+        locked = true
+        @paste (text) =>
+          func(); @copy text; locked = false

   # Return the character following the focus, and leave the selection unchanged.
   nextCharacter: ->
@@ -210,7 +222,6 @@
     @commands = {}
     @keyQueue = ""
     @keypressCount = 0
-    @yankedText = ""
     super options

     # Aliases.
@@ -218,9 +229,7 @@
     @movements.W = @movements.w

     if @options.immediateMovement
-      # Run a single movement then yank the result (note, yank() exits).
       @handleMovementKeyChar @options.immediateMovement, @getCountPrefix()
-      @yank()
       return

     @push
@@ -243,7 +252,6 @@

               else if @movements[command]
                 @handleMovementKeyChar command, @getCountPrefix()
-                @yank() if @options.oneMovementOnly
                 return @suppressEvent

         @continueBubbling
@@ -258,11 +266,11 @@
         when "function"
           @movements[keyChar].call @, count
       @scrollIntoView()
+      @yank() if @options.oneMovementOnly

   # Yank the selection; always exits; either deletes the selection or collapses it; returns the yanked text.
   yank: (args = {}) ->
     @yankedText = @selection.toString()
-    console.log "yank:", @yankedText if @debug

     if args.deleteFromDocument or @options.deleteFromDocument
       @selection.deleteFromDocument()
@@ -371,11 +379,6 @@

     unless @options.parentMode
       @installFindMode()
-
-    # Grab the initial clipboard contents.  We try to keep them intact until we get an explicit yank.
-    @clipboardContents = ""
-    @paste (text) =>
-      @clipboardContents = text if text
     #
     # End of VisualMode constructor.

@@ -387,22 +390,16 @@
         document.activeElement.blur()

     super event, target
-    @copy @yankedText if @yankedText
+    if @yankedText? and not @options.noCopyToClipboard
+      console.log "yank:", @yankedText if @debug
+      @copy @yankedText, true

   selectLine: (count, collapse) ->
     @runMovement backward, "lineboundary"
     @collapseSelectionToFocus() if collapse
     @runMovement forward, "line" for [0...count]

-  # This is used whenever manipulating the selection may, as a side effect, change the clipboard contents.  It
-  # reinstalls the original clipboard contents when we're done.
-  protectClipboard: (func) ->
-    func()
-    @copy @clipboardContents if @clipboardContents
-
-  copy: (text) ->
-    super @clipboardContents = text
-
+  # This installs a basic binding for find mode, "n" and "N".
   # FIXME(smblott).  This is a mess, it needs to be reworked.  Ideally, incorporate FindMode.
   installFindMode: ->
     previousFindRange = null
@@ -505,8 +502,8 @@
       V: -> @launchSubMode VisualLineMode

       Y: (count) -> @enterVisualModeForMovement count, immediateMovement: "Y"
-      x: (count) -> @enterVisualModeForMovement count, immediateMovement: "l", deleteFromDocument: true
-      X: (count) -> @enterVisualModeForMovement count, immediateMovement: "h", deleteFromDocument: true
+      x: (count) -> @enterVisualModeForMovement count, immediateMovement: "l", deleteFromDocument: true, noCopyToClipboard: true
+      X: (count) -> @enterVisualModeForMovement count, immediateMovement: "h", deleteFromDocument: true, noCopyToClipboard: true
       y: (count) -> @enterVisualModeForMovement count, yankLineCharacter: "y"
       d: (count) -> @enterVisualModeForMovement count, yankLineCharacter: "d", deleteFromDocument: true
       c: (count) -> @enterVisualModeForMovement count, deleteFromDocument: true, onYank: => @enterInsertMode()
@@ -517,17 +514,18 @@
       J: (count) ->
         for [0...count]
           @runMovement forward, "lineboundary"
-          @enterVisualModeForMovement 1, immediateMovement: "w", deleteFromDocument: true
+          @enterVisualModeForMovement 1, immediateMovement: "w", deleteFromDocument: true, noCopyToClipboard: true
           DomUtils.simulateTextEntry @element, " "

       r: (count) ->
         handlerStack.push
           _name: "repeat-character"
-          keydown: (event) =>
+          keydown: (event) => DomUtils.suppressPropagation event; @stopBubblingAndFalse
+          keypress: (event) =>
             handlerStack.remove()
-            if KeyboardUtils.isPrintable event
-              keyChar = KeyboardUtils.getKeyChar(event).toLowerCase()
-              @enterVisualModeForMovement count, immediateMovement: "l", deleteFromDocument: true
+            keyChar = String.fromCharCode event.charCode
+            if keyChar.length == 1
+              @enterVisualModeForMovement count, immediateMovement: "l", deleteFromDocument: true, noCopyToClipboard: true
               DomUtils.simulateTextEntry @element, [0...count].map(-> keyChar).join ""
             @suppressEvent

@@ -545,6 +543,7 @@
     @enterVisualModeForMovement count,
       immediateMovement: if immediate then "l" else null
       deleteFromDocument: true
+      noCopyToClipboard: true
       onYank: (text) =>
         chars =
           for char in text.split ""
@@ -590,19 +589,6 @@
     DomUtils.simulateTextEntry @element, "\n"
     @runMovement backward, character if direction == backward
     @enterInsertMode()
-
-  # This used whenever manipulating the selection may, as a side effect, change the clipboard contents.  We
-  # restore the original clipboard contents when we're done. Note, this may be asynchronous.  We use this
-  # approach (as opposed to the simpler, synchronous method used by Visual mode) because the user may wish to
-  # select text with the mouse (while edit mode is active), and then paste with "p" or "P".
-  protectClipboard: do ->
-    locked = false
-
-    (func) ->
-      if locked then func()
-      else
-        locked = true
-        @paste (text) => func(); @copy text; locked = false

   exit: (event, target) ->
     super event, target


FIX: remove space from end of comment

FIX: remove space from end of comment

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


detect and update align status in toolbar

--- src/modules/toolbar.coffee
+++ src/modules/toolbar.coffee
@@ -87,12 +87,28 @@
     )

   _getActive: (range) ->
+    leafFormats = this._getLeafActive(range)
+    lineFormats = this._getLineActive(range)
+    return _.defaults(leafFormats, lineFormats)
+
+  _getLeafActive: (range) ->
     if range.isCollapsed()
       start = Math.max(0, range.start - 1)
       contents = @quill.getContents(start, range.end)
     else
       contents = @quill.getContents(range)
     formatsArr = _.map(contents.ops, 'attributes')
+    return this._intersectFormats(formatsArr)
+
+  _getLineActive: (range) ->
+    formatsArr = []
+    [firstLine, offset] = @quill.editor.doc.findLineAt(range.start)
+    [lastLine, offset] = @quill.editor.doc.findLineAt(range.end)
+    lastLine = lastLine.next if lastLine? and lastLine == firstLine
+    while firstLine? and firstLine != lastLine
+      formats = { 'align': firstLine.formats['align'] }    # TODO fix when we have more line attributes
+      formatsArr.push(firstLine.formats)
+      firstLine = firstLine.next
     return this._intersectFormats(formatsArr)

   _intersectFormats: (formatsArr) ->


Allow to define queryString on fetching inventory entries.

--- src/coffee/updater/inventory-updater.coffee
+++ src/coffee/updater/inventory-updater.coffee
@@ -54,9 +54,9 @@
             deferred.reject 'Problem on creating channel: ' + body
     deferred.promise

-  allInventoryEntries: (rest) ->
+  allInventoryEntries: (rest, queryString = 'limit=0') ->
     deferred = Q.defer()
-    rest.GET '/inventory?limit=0', (error, response, body) ->
+    rest.GET "/inventory?#{queryString}", (error, response, body) ->
       if error
         deferred.reject 'Error on getting all inventory entries: ' + error
       else if response.statusCode isnt 200
@@ -65,9 +65,9 @@
         deferred.resolve body.results
     deferred.promise

-  initMatcher: ->
+  initMatcher: (queryString) ->
     deferred = Q.defer()
-    @allInventoryEntries(@rest).then (existingEntries) =>
+    @allInventoryEntries(@rest, queryString).then (existingEntries) =>
       @existingInventoryEntries = existingEntries
       deferred.resolve existingEntries
     .fail (msg) ->


Fix typo

Fix typo

--- src/objects/remotes.coffee
+++ src/objects/remotes.coffee
@@ -104,7 +104,7 @@
   # Alias for iojs.exe
   return if nodew.n == self.dist
   home = vid self, true
-  unless (src = file home, remote.dist + nodew.x).y()
+  unless (src = file home, self.dist + nodew.x).y()
     return
   if (dst = file home, nodew.n + nodew.x).y()
     return


clean up

clean up

--- app/assets/javascripts/bootstrap.js.coffee
+++ app/assets/javascripts/bootstrap.js.coffee
@@ -1,7 +1,8 @@
 jQuery ->
   $("a[rel=popover]").popover()
+  $('[rel=popover]').popover(placement: 'top', trigger: 'click')
+  $('[rel=tooltip]').tooltip()
   $(".tooltip").tooltip()
-  $("a[rel=tooltip]").tooltip()
   window.prettyPrint && prettyPrint()

 jQuery ->
@@ -22,7 +23,3 @@

   $(window).scroll ->
     processScroll()
-
-jQuery ->
-  $('[rel=tooltip]').tooltip()
-  $('[rel=popover]').popover(placement: 'top', trigger: 'click')


Fix updateHealth issue

Fix updateHealth issue

--- src/components/entities/BulletCannon.coffee
+++ src/components/entities/BulletCannon.coffee
@@ -17,7 +17,6 @@
       maxHealth: attr.health ? defaultHealth
       aimSpeed: 90
     )
-    @enemy()
     @barrel = Crafty.e('2D, WebGL, mineCannon, TweenPromise, Collision')
     @barrel.attr(
       x: @x - 64
@@ -27,6 +26,7 @@
       h: 32
     )
     @barrel.origin(@barrel.w - 16, 16)
+    @enemy()
     @barrel.onHit(
       'Bullet',
       (e) => @onProjectileHit(e)
@@ -45,9 +45,7 @@
       h: 1
     )
     @barrel.attach(@tip)
-
     @attach(@barrel)
-
     this

   applyBarrelHitFlash: (onOff) ->


account for situations where currentStep isn't set in ajax resp.

--- app/assets/javascripts/hat-trick.js.coffee
+++ app/assets/javascripts/hat-trick.js.coffee
@@ -68,6 +68,7 @@
         # merge new data with window.htData
         $.extend(window.htData, data)
       error: (data) =>
+        # console.log "Error response: #{data.responseText}"
         appErrors = eval "(#{data.responseText})"
         this.addErrorItem value[0] for key, value of appErrors.formModel
     ajax
@@ -219,9 +220,9 @@
         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
+      else if htData.wizardMetadata?.currentStep?
         this.showStep(htData.wizardMetadata.currentStep)

 $ ->


Correct name to be "Spaced Practice Performance"

"even though space is cool and all",

--- src/components/plan-stats/performances.cjsx
+++ src/components/plan-stats/performances.cjsx
@@ -63,7 +63,7 @@
     unless _.isEmpty(spacedPages)
       practices = _.map(spacedPages, @renderPracticeBars)
       practices =

-        Space Practice Performance
+        Spaced Practice Performance
         {practices}


status update: add signature annotations

--- workers/social/lib/social/models/messages/statusupdate/index.coffee
+++ workers/social/lib/social/models/messages/statusupdate/index.coffee
@@ -1,7 +1,7 @@
 JPost = require '../post'
 {extend} = require 'underscore'
 module.exports = class JStatusUpdate extends JPost
-  {secure, race} = require 'bongo'
+  {secure, race, signature} = require 'bongo'
   {Relationship} = require 'jraphical'
   {permit} = require '../../group/permissionset'
   {once, extend} = require 'underscore'
@@ -33,15 +33,75 @@
         { name: 'RemovedFromCollection' }
       ]
     sharedMethods     :
-      static          : [
-        'create','one','fetchDataFromEmbedly','updateAllSlugs',
-        'fetchFollowingFeed', 'fetchTopicFeed', 'fetchProfileFeed','fetchGroupActivity'
-      ]
-      instance        : [
-        'reply','restComments','commentsByRange'
-        'like','fetchLikedByes','mark','unmark','fetchTags'
-        'delete','modify','fetchRelativeComments','checkIfLikedBefore'
-      ]
+
+      static          :
+        create:
+          (signature Object, Function)
+        one:
+          (signature Object, Function)
+        fetchDataFromEmbedly: [
+          (signature String, Object, Function)
+          (signature [String], Object, Function)
+        ]
+        updateAllSlugs: [
+          (signature Function)
+          (signature Object, Function)
+        ]
+        fetchFollowingFeed: [
+          (signature Function)
+          (signature Object, Function)
+        ]
+        fetchTopicFeed: [
+          (signature Function)
+          (signature Object, Function)
+        ]
+        fetchProfileFeed: [
+          (signature Function)
+          (signature Object, Function)
+        ]
+        fetchGroupActivity: [
+          (signature Function)
+          (signature Object, Function)
+        ]
+
+      instance        :
+        reply:
+          (signature String, Function)
+        restComments: [
+          (signature Function)
+          (signature Number, Function)
+        ]
+        commentsByRange: [
+          (signature Function)
+          (signature Object, Function)
+        ]
+        like:
+          (signature Function)
+        fetchLikedByes: [
+          (signature Function)
+          (signature Object, Function)
+        ]
+        mark: [
+          (signature String)
+          (signature String, Function)
+        ]
+        unmark: [
+          (signature String)
+          (signature String, Function)
+        ]
+        fetchTags: [
+          (signature Function)
+          (signature Object, Function)
+        ]
+        delete:
+          (signature Function)
+        modify:
+          (signature Object, Function)
+        fetchRelativeComments:
+          (signature Object, Function)
+        checkIfLikedBefore:
+          (signature Function)
+
     schema            : schema
     relationships     : JPost.relationships

@@ -117,7 +177,8 @@
         if err then return callback {error: "Not allowed to open this group"}
         else callback null, group

-  @fetchGroupActivity = secure (client, options = {}, callback)->
+  @fetchGroupActivity = secure (client, options, callback)->
+    [callback, options] = [options, callback]  unless callback
     @getCurrentGroup client, (err, group)=>
       if err then return callback err
       {to} = options
@@ -130,7 +191,8 @@
         return callback err if err
         @decorateResults data, callback

-  @fetchProfileFeed = secure (client, options = {}, callback)->
+  @fetchProfileFeed = secure (client, options, callback)->
+    [callback, options] = [options, callback]  unless callback
     {connection:{delegate}, context:{group}} = client
     return callback new Error "Origin is not defined" unless options.originId

@@ -150,7 +212,8 @@
       return callback err if err
       @decorateResults data, callback

-  @fetchTopicFeed = secure (client, options = {}, callback)->
+  @fetchTopicFeed = secure (client, options, callback)->
+    [callback, options] = [options, callback]  unless callback

     {context:{group}} = client

@@ -173,7 +236,8 @@
         return callback err if err
         @decorateResults posts, callback

-  @fetchFollowingFeed = secure (client, options = {}, callback)->
+  @fetchFollowingFeed = secure (client, options, callback)->
+    [callback, options] = [options, callback]  unless callback
     {Activity} = require "../../graph"
     options.client = client
     Activity.fetchFolloweeContentsForNewKoding options, (err, ids)=>


Activity: added limit to target options

Activity: added limit to target options

--- workers/social/lib/social/models/messages/statusupdate/index.coffee
+++ workers/social/lib/social/models/messages/statusupdate/index.coffee
@@ -154,7 +154,11 @@

       {to} = options
       to = if to then new Date(to)  else new Date()
-      options.targetOptions = selector : {'meta.createdAt' : "$lt" : to }
+
+      options.targetOptions = {
+        selector: {'meta.createdAt': {"$lt": to}},
+        options:  {limit: options.limit}
+      }

       tag.fetchContents {targetName: 'JStatusUpdate'}, options, (err, posts)=>
         return callback err if err


also remove console.logs

also remove console.logs

--- lineman/app/components/navbar/navbar.coffee
+++ lineman/app/components/navbar/navbar.coffee
@@ -4,10 +4,8 @@
   templateUrl: 'generated/components/navbar/navbar.html'
   replace: true
   controller: ($scope, $rootScope, Records, ThreadQueryService, AppConfig) ->
-    console.log AppConfig.baseUrl
     parser = document.createElement('a')
     parser.href = AppConfig.baseUrl
-    console.log parser.hostname

     $scope.officialLoomio = AppConfig.baseUrl == 'https://www.loomio.org/'


Fixes #2895 - moving from Manage Knowledge base view to another Manage view requires multiple clicks

--- app/assets/javascripts/app/controllers/_manage/knowledge_base.coffee
+++ app/assets/javascripts/app/controllers/_manage/knowledge_base.coffee
@@ -51,7 +51,7 @@

   release: ->
     super
-    @modal.el.remove()
+    @modal?.el.remove()

   processLoaded: ->
     if @knowledge_base_id


moved the button again

moved the button again

--- app/assets/javascripts/species/views/downloads_button_view.js.coffee
+++ app/assets/javascripts/species/views/downloads_button_view.js.coffee
@@ -1,7 +0,0 @@
- Species.DownloadsButtonView = Ember.View.extend
-  tagName: 'a'
-  classNames: ['download']
-  template: Ember.Handlebars.compile('DOWNLOAD SPECIES LISTS')
-  templateName: 'species/downloads_button'
-  click: (event) ->
-    @set('controller.downloadsPopupVisible', true)


removed debugger

removed debugger

--- app/assets/javascripts/views/overlays/message_overlay_view.js.coffee
+++ app/assets/javascripts/views/overlays/message_overlay_view.js.coffee
@@ -1,10 +1,3 @@
 ETahi.MessageOverlayView = ETahi.OverlayView.extend
   templateName: 'overlays/message_overlay'
   layoutName: 'layouts/assignee_overlay_layout'
-
-  didInsertElement: (e) ->
-    $("div.unread").bind 'inview', (e, visible) ->
-      debugger
-
-
-


activity: handle private message selected channel show properly

--- client/activity/lib/components/privatemessagethreadpane/index.coffee
+++ client/activity/lib/components/privatemessagethreadpane/index.coffee
@@ -23,6 +23,7 @@
       channelThread         : getters.selectedChannelThread
       channelThreadMessages : getters.selectedChannelThreadMessages
       channelParticipants   : getters.selectedChannelParticipants
+      followedChannels      : getters.followedPrivateChannelThreads
     }

@@ -36,10 +37,10 @@
       channelParticipants   : immutable.List()

-  componentDidMount: -> reset @props
+  componentDidMount: -> reset @props, @state

-  componentWillReceiveProps: (nextProps) -> reset nextProps
+  componentWillReceiveProps: (nextProps) -> reset nextProps, @state

   renderHeader: ->
@@ -89,19 +90,25 @@

 React.Component.include.call PrivateMessageThreadPane, [KDReactorMixin]

-reset = (props) ->
+reset = (props, state) ->

+  { followedChannels, channelThread } = state
   { privateChannelId } = props.routeParams
   {
     thread : threadActions,
     channel : channelActions,
     message : messageActions } = ActivityFlux.actions

+  unless privateChannelId
+    unless channelThread
+      botChannel = kd.singletons.socialapi.getPrefetchedData 'bot'
+      privateChannelId = botChannel.id
+
   if privateChannelId
     channelActions.loadChannel('private', privateChannelId).then ({ channel }) ->
       threadActions.changeSelectedThread channel.id
       messageActions.loadMessages channel.id
       channelActions.loadParticipants channel.id, channel.participantsPreview
-  else
-    thread.changeSelectedThread null
+  else if not channelThread
+    threadActions.changeSelectedThread null


Client: Activity: Feed: Updated SinglePublicChannelPopularMessages to change path and onEnter functionality. It is not child route of SingleChannelRoute anymore

--- client/activity/lib/routes/singlepublicchannelpopularmessages.coffee
+++ client/activity/lib/routes/singlepublicchannelpopularmessages.coffee
@@ -1,14 +1,19 @@
-ActivityFlux      = require 'activity/flux'
-ResultStates      = require 'activity/util/resultStates'
-ChannelThreadPane = require 'activity/components/channelthreadpane'
-{ channelByName } = ActivityFlux.getters
-{ channel : channelActions } = ActivityFlux.actions
+kd                  = require 'kd'
+ActivityFlux        = require 'activity/flux'
+ResultStates        = require 'activity/constants/resultStates'
+ChannelThreadPane   = require 'activity/components/channelthreadpane'
+transitionToChannel = require 'activity/util/transitionToChannel'
+{ channelByName }   = ActivityFlux.getters
+
+{
+  thread  : threadActions,
+  channel : channelActions } = ActivityFlux.actions

 module.exports = class SinglePublicChannelPopularMessages

   constructor: ->

-    @path = 'Liked'
+    @path = ':channelName/Liked'

   getComponents: (state, callback) ->
@@ -21,13 +26,19 @@
   onEnter: (nextState, replaceState, done) ->

     { channelName } = nextState.params
-    channel = channelByName channelName

-    channelActions.changeResultState channel._id, ResultStates.LIKED
-    channelActions.loadPopularMessages(channel._id).then -> done()
+    channelActions.loadChannelByName(channelName).then ({channel}) ->
+      channelActions.changeResultState channel._id, ResultStates.LIKED
+      channelActions.loadPopularMessages(channel._id).then -> done()
+
+    if channelName
+      transitionToChannel channelName, done
+    else if not selectedThread
+      threadActions.changeSelectedThread null
+      done()
+    else
+      done()

-  onLeave: ->
+  onLeave: -> actions.channel.changeResultState channel._id, ResultStates.RECENT

-    actions.channel.changeResultState channel._id, ResultStates.RECENT
-


Fetch carousel data

Fetch carousel data

--- apps/galleries_institutions2/routes.coffee
+++ apps/galleries_institutions2/routes.coffee
@@ -4,20 +4,31 @@
 PartnerCellGrid = require './components/partner_cell_grid/view'
 Backbone = require 'backbone'
 Q = require 'bluebird-q'
+PrimaryCarousel = require './components/primary_carousel/fetch'

 @galleries = (req, res, next) ->
-  categories = new PartnerCategories()
-  categories.fetchUntilEnd data: category_type: 'Gallery'
+  carousel = new PrimaryCarousel
+  categories = new PartnerCategories
+
+  Q.all [
+    carousel.fetch()
+    categories.fetchUntilEnd data: category_type: 'Gallery'
+  ]
     .then ->
-
       Q.all categories.map (category) ->
         category.related().partners.fetch data: size: 10

-      .then ->
-        filteredCategories = categories.select (category) ->
-          category.related().partners.length
+    .then ->
+      filteredCategories = categories.select (category) ->
+        category.related().partners.length

-        res.render 'index', categories: filteredCategories, type: 'gallery'
+      res.render 'index',
+        type: 'gallery'
+        carousel: carousel
+        categories: filteredCategories
+
+    .catch next
+    .done()

 @institutions = (req, res, next) ->
   categories = new PartnerCategories()


Coffeescript adjusted for 70d7715413df4e8

--- architecture-examples/spine/src/app.coffee
+++ architecture-examples/spine/src/app.coffee
@@ -79,7 +79,7 @@
 		text = (count) -> if count is 1 then 'item' else 'items'
 		active = Todo.active().length
 		completed = Todo.completed().length
-		@count.html "#{ active } #{ text active } left"
+		@count.html "#{ active } #{ text active } left"
 		@clearCompleted.text "Clear completed (#{ completed })"


fix: search_entities default "format" value is invalid

The example given in readme doesn't work, this should fix it.

Url generated : https://www.wikidata.org/w/api.php?action=wbsearchentities&search=Ingmar%20Bergman&language=fr&limit=10&format=20

Error returned :
```
"error": {
        "code": "unknown_format",
        "info": "Unrecognized value for parameter 'format': 20",
        "*": "See https://www.wikidata.org/w/api.php for API usage"
    }
```

--- src/queries/wikidata_search_entities.coffee
+++ src/queries/wikidata_search_entities.coffee
@@ -11,7 +11,7 @@

   language or= 'en'
   limit or= '20'
-  format or= '20'
+  format or= 'json'

   return buildUrl 'wikidata',
     action: 'wbsearchentities'


realtime: authenticate users before channel subscription

--- client/Main/realtimecontroller.coffee
+++ client/Main/realtimecontroller.coffee
@@ -11,6 +11,17 @@
     {subscribekey} = KD.config.pubnub

     @pubnub = PUBNUB.init subscribe_key : subscribekey
+  # channel authentication is needed for notification channel and
+  # private channels
+  authenticate: (options, callback) ->
+
+    return callback null  unless options?
+
+    { endPoint, data } = options
+    return callback { message : "endPoint is not set"}  unless endPoint
+
+    KD.utils.doXhrRequest {endPoint, data}, callback
+

   # subscriptionData =
   #   serviceType: 'socialapi'
@@ -26,47 +37,39 @@
     data = {name: channelName, typeConstant, group}
     KD.utils.doXhrRequest {endPoint, data}, callback

-  subscribe: (options = {}, callback = noop) ->

-    pubnubChannelName = prepareChannelName options
+
+  subscribePubnub: (options = {}, callback) ->
+
+    pubnubChannelName = options.channelName

     # return channel if it already exists
-    return @channels[pubnubChannelName]  if @channels[pubnubChannelName]
+    return callback null, @channels[pubnubChannelName]  if @channels[pubnubChannelName]

-    channelInstance = new PubnubChannel name: pubnubChannelName
-    @channels[pubnubChannelName] = channelInstance
+    @authenticate options.authenticate, (err) =>

-    @pubnub.subscribe
-      channel : pubnubChannelName
-      message : (message, env, channel) =>
-        return  unless message
-        {eventName, body} = message
+      return callback err  if err

-        # no need to emit any events when not subscribed
-        return  unless @channels[channel]
+      realtimeToken = Cookies.get("realtimeToken")
+      @pubnub.auth realtimeToken

-        @channels[channel].emit eventName, body
-      connect : -> callback null
-      error   : (err) -> callback err # not sure if it is really sending an error
-      restore : yes
+      channelInstance = new PubnubChannel name: pubnubChannelName

-    return channelInstance
+      @pubnub.subscribe
+        channel : pubnubChannelName
+        message : (message, env, channel) =>
+          return  unless message
+          {eventName, body} = message

-  prepareChannelName = (options) ->
-    { eventType } = options
-    switch eventType
-      when 'channel'
-        { channelName, typeConstant, group, token } = options
-        return "channel-#{token}-#{group}-#{typeConstant}-#{channelName}"
-      when 'instance'
-        { token } = options
-        return "instance-#{token}"
-      when 'notification'
-        {nickname} = options
-        {environment} = KD.config
-        return "notification-#{environment}-#{nickname}"
+          # no need to emit any events when not subscribed
+          return  unless @channels[channel]

-    return ""
+          @channels[channel].emit eventName, body
+        connect : =>
+          @channels[pubnubChannelName] = channelInstance
+          callback null, channelInstance
+        error   : (err) -> callback err # not sure if it is really sending an error
+        restore : yes

 class PubnubChannel extends KDObject


properly build from field in forward message header

--- client/app/utils/message_utils.coffee
+++ client/app/utils/message_utils.coffee
@@ -110,7 +110,6 @@
                     inReplyTo.get('cc')).filter (dest) ->
                     return dest? and toAddresses.indexOf(dest.address) is -1
                 message.bcc = []
-                message.subject = @getReplySubject inReplyTo
                 message.text = separator + @generateReplyText(text) + "\n"
                 message.html = """

#{separator} …

@@ -123,12 +122,23 @@
                    .map (address) -> address.address
                    .join ', '

+                senderInfos = @getReplyToAddress inReplyTo
+                senderName = ""
+                senderAddress = ""
+                if senderInfos.length > 0
+                    senderName = senderInfos[0].name
+                    senderAddress = senderInfos[0].address
+
+                senderString = senderAddress
+                if senderName.length > 0
+                    fromField = "#{senderName} <#{senderAddress}>"
+
                 separator = """

 ----- #{t 'compose forward header'} ------
 #{t 'compose forward subject'} #{inReplyTo.get 'subject'}
 #{t 'compose forward date'} #{dateHuman}
-#{t 'compose forward from'} #{sender}
+#{t 'compose forward from'} #{fromField}
 #{t 'compose forward to'} #{addresses}

 """


return nothing if evidence type unset

return nothing if evidence type unset

--- client/coffee/abstractEvidence.coffee
+++ client/coffee/abstractEvidence.coffee
@@ -20,8 +20,9 @@

     object_list: ->
         key = Session.get('evidenceType')
-        Collection = share.evidenceType[key].collection
-        return Collection.find({}, {sort: {sortIdx: 1}})
+        if key?
+            Collection = share.evidenceType[key].collection
+            return Collection.find({}, {sort: {sortIdx: 1}})

 share.abstractTblEvents =


webapp/projects: removing console.log line

--- src/smc-webapp/projects/create-project.cjsx
+++ src/smc-webapp/projects/create-project.cjsx
@@ -33,7 +33,7 @@
         redux.getActions('billing')?.update_customer()

     cancel_editing: ->
-        console.log 'cancel_editing'
+        #console.log 'cancel_editing'
         @setState
             state      : 'view'
             title_text : ''


Updating tests to support projects names with spaces.

--- src/test/hubot-jenkins-notifier_test.coffee
+++ src/test/hubot-jenkins-notifier_test.coffee
@@ -63,7 +63,7 @@
 test_data = [
   {
     "name": "finished-failed",
-    "expected_out": "JobName build #1 started failing: http://ci.jenkins.org/job/project/5",
+    "expected_out": "JobName build #1 started failing: http://ci.jenkins.org/job/project name/5",
     "body": {
       "name":"JobName",
       "url":"JobUrl",
@@ -71,15 +71,15 @@
         "number":1,
         "phase":"FINISHED",
         "status":"FAILURE",
-        "url":"job/project/5",
-        "full_url":"http://ci.jenkins.org/job/project/5"
+        "url":"job/project%20name/5",
+        "full_url":"http://ci.jenkins.org/job/project%20name/5"
         "parameters":{"branch":"master"}
       }
     }
   },
   {
     "name": "finished-failed-nofull_url",
-    "expected_out": "JobName build #1 started failing: job/project/5",
+    "expected_out": "JobName build #1 started failing: job/project name/5",
     "body": {
       "name":"JobName",
       "url":"JobUrl",
@@ -87,7 +87,7 @@
         "number":1,
         "phase":"FINISHED",
         "status":"FAILURE",
-        "url":"job/project/5",
+        "url":"job/project%20name/5",
         "parameters":{"branch":"master"}
       }
     }
@@ -102,8 +102,8 @@
         "number":2,
         "phase":"STARTED",
         "status":"FAILED",
-        "url":"job/project/5",
-        "full_url":"http://ci.jenkins.org/job/project/5"
+        "url":"job/project%20name/5",
+        "full_url":"http://ci.jenkins.org/job/project%20name/5"
         "parameters":{"branch":"master"}
       }
     }


Added fog on the tyne

Added fog on the tyne

--- src/scripts/geordie.coffee
+++ src/scripts/geordie.coffee
@@ -3,6 +3,7 @@
 #
 # Commands:
 #   hubot whey-aye man
+#   hubot fog on the tyne
 #
 # Author:
 #  pstack
@@ -15,9 +16,10 @@
   "http://i2.cdnds.net/13/31/618x579/uktv-auf-wiedersehen-pet.jpg",
   "http://eil.com/images/main/Lindisfarne+-+Fog+On+The+Tyne+%5BRevisited%5D+-+5%22+CD+SINGLE-494879.jpg",
   "https://s3.amazonaws.com/uploads.hipchat.com/64658/539392/B3WShhDa0HOiKZ3/upload.png",
+  "http://i2.cdnds.net/11/17/reality_tv_geordie_shore_jay.jpg"
 ]

 module.exports = (robot) ->

   robot.respond /(whey-aye man|fog on the tyne)/i, (msg) ->
-    msg.send msg.random geordies+    msg.send msg.random geordies


Removed preformatting from daily report

Removed preformatting from daily report

--- src/scripts/payroll.coffee
+++ src/scripts/payroll.coffee
@@ -56,11 +56,11 @@
         recorded = true
       if not recorded
         offBuffer += "#{report.extra.slack}:\t0 hours\n"
-    response += "```" + logBuffer + "```\n"
+    response += logBuffer + "\n"
     if offBuffer.length > 0
       response += "DAILY OFF-TIME LOG:
                    *#{yesterday.format('dddd MMMM D YYYY').toUpperCase()}*\n"
-      response += "```" + offBuffer + "```\n"
+      response += offBuffer + "\n"
     upcomingEvents = Organization.calendar.upcomingEvents()
     if upcomingEvents.length > 0
       now = moment().subtract(1, 'days')


storage: setInterval -- don't overflow

storage: setInterval -- don't overflow

--- src/smc-hub/storage.coffee
+++ src/smc-hub/storage.coffee
@@ -1297,6 +1297,17 @@
         return
     async.series([
         (cb) ->
+            dbg("ensure projects zpool is imported")
+            misc_node.execute_code
+                command : 'zpool'
+                args    : ['import', 'projects']
+                timeout : 180
+                cb      : (err,output) ->
+                    if err and output?.stderr?.indexOf('already exists') == -1
+                        cb(err)
+                    else
+                        cb()
+        (cb) ->
             dbg("connect to database")
             require('smc-hub/rethink').rethinkdb
                 hosts : DB
@@ -1394,10 +1405,11 @@
             #      echo "options zfs zfs_expire_snapshot=8388608" >> /etc/modprobe.d/zfs.conf
             task_ensure_zfs_snapshots_are_mounted () ->
                 task_mount_snapshots_on_all_compute_vms()
-
-            # anything more than 8388608s (=2^23s) for the zfs_expire_snapshot parameter fails to work, causing instant unmount :-(
-            setInterval(task_ensure_zfs_snapshots_are_mounted, 8388608*1000)
-
+
+            zfs_expire_snapshot = 8388608
+            # anything more than 8388608s (=2^23s = 97 days!) for the zfs_expire_snapshot parameter fails to work, causing instant unmount :-(
+            # Math.min due to http://stackoverflow.com/questions/12633405/what-is-the-maximum-delay-for-setinterval
+            setInterval(task_ensure_zfs_snapshots_are_mounted, Math.min(2**31-1, zfs_expire_snapshot*1000))

             cb()
     ], (err) ->


switch stats link back to not using share server, so always updated

--- src/smc-webapp/r_help.cjsx
+++ src/smc-webapp/r_help.cjsx
@@ -193,7 +193,7 @@

                 {@render_recent_usage_stats()}
                 <Icon name='line-chart' fixedWidth />{' '}
-
+
                 More data...


:bug:Force primer level language in home version

--- app/lib/LevelSetupManager.coffee
+++ app/lib/LevelSetupManager.coffee
@@ -151,9 +151,10 @@
     PlayLevelView = 'views/play/level/PlayLevelView'
     LadderView = 'views/ladder/LadderView'
     viewClass = if @options.levelPath is 'ladder' then LadderView else PlayLevelView
+    route = "/play/#{@options.levelPath || 'level'}/#{@options.levelID}"
+    route += "?codeLanguage=" + @level.get('primerLanguage') if @level.get('primerLanguage')
     Backbone.Mediator.publish 'router:navigate', {
-      route: "/play/#{@options.levelPath || 'level'}/#{@options.levelID}"
-      viewClass: viewClass
+      route, viewClass
       viewArgs: [{supermodel: @supermodel}, @options.levelID]
     }


open share.appgyver.com after successful cloud deployment

--- src/steroids/Deploy.coffee
+++ src/steroids/Deploy.coffee
@@ -5,6 +5,7 @@
 restify = require "restify"
 restler = require "restler"
 async = require "async"
+open = require "open"

 paths = require "./paths"
 DeployConverter = require "./DeployConverter"
@@ -121,6 +122,9 @@
     fs.writeFileSync paths.cloudConfigJSON, JSON.stringify(config)

     util.log "Deployment complete"
+
+    open "http://share.appgyver.com/?id=#{config.id}&hash=#{config.identification_hash}"
+
     process.exit 0

 module.exports = Deploy


Set tags in template to empty array

Set tags in template to empty array

--- src/stores/exercise.coffee
+++ src/stores/exercise.coffee
@@ -168,6 +168,7 @@
     getTemplate: (id) ->
       questionId = QuestionStore.freshLocalId()

+      tags: _.clone []
       stimulus_html:"",
       questions:[_.extend({}, QuestionStore.getTemplate(), {id: questionId})]


No dupes, only add tag if it doesn't exist.

--- src/stores/exercise.coffee
+++ src/stores/exercise.coffee
@@ -28,7 +28,8 @@

   # Updates or creates a prefixed tag
   # If previous is given, then only the tag with that value will be updated
-  # Otherwise, all other tags with prefix will be removed
+  # Otherwise it will be added (unless it exists)
+  # If replaceOthers is set, all others will prefix will be removed
   setPrefixedTag: (id, {prefix, tag, tags, previous, replaceOthers}) ->
     prefix += ':'
     if tags
@@ -42,7 +43,7 @@
     if previous?
       tags = _.reject tags, (tag) -> tag is prefix + previous

-    if tag
+    if tag and not _.find(tags, (tag) -> tag is prefix + tag)
       tags.push(prefix + tag)

     @_change(id, {tags})


Tests: Environments: Helpers: Fixed helpers.

--- tests/src/helpers/environmenthelpers.coffee
+++ tests/src/helpers/environmenthelpers.coffee
@@ -43,7 +43,7 @@

   seeUpgradeModal: (browser) ->

-    clickAddVMButton(browser)
+    @clickAddVMButton(browser)
     browser.waitForElementVisible '.computeplan-modal.free-plan .kdmodal-inner', 20000 # Assertion

@@ -57,9 +57,9 @@
     createDomainName  = paragraph.split(' ')[0]
     domainName        = createDomainName + '.' + user.username + '.dev.koding.io'

-    openVmSettingsModal(browser)
+    @openVmSettingsModal(browser)

-    clickMoreButtonInVMSettingsModal(browser)
+    @clickMoreButtonInVMSettingsModal(browser)

     browser
       .waitForElementVisible    '.more-form .domains', 20000


Append to querystring

Append to querystring

--- apps/articles/client/show.coffee
+++ apps/articles/client/show.coffee
@@ -91,7 +91,7 @@
   playVideo: (e) ->
     $cover = $(e.currentTarget).parent()
     $iframe = $cover.next('.articles-video').find('iframe')
-    $newIframe = $iframe.clone().attr('src', $iframe.attr('src') + '?autoplay=1')
+    $newIframe = $iframe.clone().attr('src', $iframe.attr('src') + '&autoplay=1')
     $iframe.replaceWith $newIframe
     $cover.remove()


Show recently updated blocks in a channel by all user

--- apps/user/queries/profile.coffee
+++ apps/user/queries/profile.coffee
@@ -17,7 +17,7 @@
             counts {
               blocks
             }
-            blocks(per: $perBlocks, sort_by: UPDATED_AT, direction: DESC, type: BLOCK, user_id: $id) {
+            blocks(per: $perBlocks, sort_by: UPDATED_AT, direction: DESC, type: BLOCK) {
               ... blockThumb
             }
           }


fix issue about account add

fix issue about account add

--- coffee/controllers/accountCtrl.coffee
+++ coffee/controllers/accountCtrl.coffee
@@ -71,7 +71,6 @@
    add account.
   ###
   addAccount = (msg) ->
-    $scope.isSaving = $scope.isAdding = false
     if msg?.user?.id?
       account =
         url:    $scope.option.url
@@ -81,6 +80,7 @@
         userId: msg.user.id
       Account.addAccount account, (result) ->
         if result
+          $scope.isSaving = $scope.state.isAdding = false
           Message.toast Resource.string("msgAuthSuccess"), 3000
           loadProject account
         else
@@ -94,7 +94,7 @@
   ###
   failAuthentication = (msg) ->
     $scope.isSaving = false
-    Message.toast Resource.string("msgAuthFail")
+    Message.toast Resource.string("msgAuthFail"), 3000

   ###


Shorten exports to X

Shorten exports to X

--- webpack/cmdize.coffee
+++ webpack/cmdize.coffee
@@ -31,7 +31,7 @@

       fs.writeFile path.format(x), """
         #{prolog}#{bat.before or ''}#{sword bat.command}"%~f0"#{word bat.args}
-        #{bat.after or ''}#{yml[':epilog']}#{do z.source}
+        #{bat.after or ''}#{yml[':epilog']}#{reexport debug, z.source()}

         """, ->
     return
@@ -59,3 +59,11 @@
     res.push "#{s}:debug"
     res.push s
   res
+
+reexport = (debug, text)->
+  if debug
+    text
+  else
+    text
+    .replace /[.]exports\b/g, '.X'
+    .replace /exports/, 'X'       # First: module = {exports: {}, ...}


add missing poll service icons

add missing poll service icons

--- vue/src/shared/services/poll_service.coffee
+++ vue/src/shared/services/poll_service.coffee
@@ -19,6 +19,7 @@
       perform: -> Session.user() && poll.translate(Session.user().locale)

     show_results:
+      icon: 'mdi-eye-outline'
       name: 'poll_common_card.show_results'
       dock: 2
       canPerform: ->
@@ -27,6 +28,7 @@
         poll.pleaseShowResults = true

     hide_results:
+      icon: 'mdi-eye-off-outline'
       name: 'poll_common_card.hide_results'
       dock: 2
       canPerform: ->
@@ -168,6 +170,7 @@

     export_poll:
       name: 'common.action.export'
+      icon: 'mdi-database-arrow-right-outline'
       menu: true
       canPerform: ->
         AbilityService.canExportPoll(poll)
@@ -176,6 +179,7 @@

     print_poll:
       name: 'common.action.print'
+      icon: 'mdi-printer-outline'
       menu: true
       canPerform: ->
         AbilityService.canExportPoll(poll)
@@ -200,6 +204,7 @@
     discard_poll:
       name: 'common.action.delete'
       menu: true
+      icon: 'mdi-delete'
       canPerform: ->
         AbilityService.canDeletePoll(poll)
       perform: ->
@@ -216,6 +221,7 @@
     add_poll_to_thread:
       menu: true
       name: 'action_dock.add_poll_to_thread'
+      icon: 'mdi-comment-plus-outline'
       canPerform: ->
         AbilityService.canAddPollToThread(poll)
       perform: ->


Fix ssh key form when returning after form was open

When user opens ssh key form, navigates to a different rote and returns, ssh key
form shows that a custom key is added, but it's just a leftover from the
previous open of the form. It seems that Ember Model caches the new model in
the store and returns it from fetch. The fix is to return null in such case.

--- assets/scripts/app/routes.coffee
+++ assets/scripts/app/routes.coffee
@@ -476,7 +476,7 @@
   model: (params) ->
     repo = @modelFor('repo')
     self = this
-    Travis.SshKey.fetch(repo.get('id')).then ( (result) -> result ), (xhr) ->
+    Travis.SshKey.fetch(repo.get('id')).then ( (result) -> result unless result.get('isNew') ), (xhr) ->
       if xhr.status == 404
         # if there is no model, just return null. I'm not sure if this is the
         # best answer, maybe we should just redirect to different route, like
@@ -495,3 +495,8 @@
     if @defaultKey
       controller.set('defaultKey', @defaultKey)
       @defaultKey = null
+
+  deactivate: ->
+    @_super.apply(this, arguments)
+
+    @controllerFor('ssh_key').send('cancel')


remove typo

remove typo

--- config/initializers/resetproxy.coffee
+++ config/initializers/resetproxy.coffee
@@ -16,7 +16,7 @@
             appDict = {}
             if installedApps isnt undefined
                 for installedApp in installedApps
-                    if installeApp.name isnt ""
+                    if installedApp.name isnt ""
                         appDict[installedApp.name] = installedApp
                     else
                         installedApp.destroy()


fix(app generator): remove typo from coffeelint:test task's file declaration

--- generators/app/templates/@Gruntfile.coffee
+++ generators/app/templates/@Gruntfile.coffee
@@ -364,7 +364,7 @@

             test:
                 files: [
-                    src:                '<%= build.test %>**/*.coffee %>'
+                    src:                '<%= build.test %>**/*.coffee'
                 ]


GitProvider.test: re-generate new repo owner on each tests

--- workers/social/lib/social/models/gitprovider/gitprovider.test.coffee
+++ workers/social/lib/social/models/gitprovider/gitprovider.test.coffee
@@ -46,10 +46,12 @@
           readme      =
             content   : 'This is awesome stack template!!!'

-          originalUrl = "https://github.com/koding/#{generateRandomString()}"
+          user        = 'koding'
+          repo        = generateRandomString()
+          originalUrl = "https://github.com/#{user}/#{repo}"
           title       = 'Imported stack template'

-          importData = { template, readme, originalUrl }
+          importData = { template, readme, originalUrl, user, repo }

           GitProvider.createImportedStackTemplate client, title, importData, (err, _template) ->
             expect(err?.message)                               .not.exist


Prefetch: fix activity fetcher

Prefetch: fix activity fetcher

--- workers/social/lib/social/cache/feed.coffee
+++ workers/social/lib/social/cache/feed.coffee
@@ -15,14 +15,15 @@
       return cb null, [] if err
       group._fetchMembersFromGraph client, {}, cb

-  fetchActivityFromGraph = (bongoModels, client, cb)->
+  fetchActivity = (bongoModels, client, cb)->
     return cb null, [] unless bongoModels
-    {CActivity} = bongoModels
-    options = facets : "Everything"
+    {JNewStatusUpdate} = bongoModels
+    # options = facets : "Everything"

-    CActivity._fetchPublicActivityFeed client, options, (err, data)->
+    JNewStatusUpdate._fetchGroupActivity client, options, (err, data)->
       return cb null, [] if err
       return cb null, data
+

   # set interval options for later use
   intervalOptions = options
@@ -53,7 +54,7 @@

   # we are fetching group activity, so again we can return this one for all groups
   queue.push ->
-    fetchActivityFromGraph bongoModels, client, (err, activity)=>
+    fetchActivity bongoModels, client, (err, activity)=>
       localPrefetchedFeeds['activity.main'] = activity  if activity
       queue.fin()


app slug duplicate error fixed for app publishing when app already published

--- workers/social/lib/social/models/app.coffee
+++ workers/social/lib/social/models/app.coffee
@@ -173,6 +173,22 @@
                     originId    : delegate.getId()
                     originType  : delegate.constructor.name
                     identifier  : "waits.for.approve:#{data.identifier}"
+
+                  approval_app.createSlug (err, slug)->
+                    if err
+                      callback err
+                    else
+                      approval_app.slug   = "waits-for-approve-#{data.manifest.version}-#{slug.slug}"
+                      approval_app.slug_  = "waits-for-approve-#{data.manifest.version}-#{slug.slug}"
+                      approval_app.save (err)->
+                        if err
+                          callback err
+                        else
+                          approval_app.addCreator delegate, (err)->
+                            if err
+                              callback err
+                            else
+                              callback null, app

                   approval_app.save (err)->
                     if err