Replaced with sprite

Replaced with sprite

--- app/scripts/scenery/City.coffee
+++ app/scripts/scenery/City.coffee
@@ -38,24 +38,9 @@
   if mode is 'start'
     e = Crafty.e('2D, Canvas, Image').image('images/horizon-city-start.png').attr({ z: -600 })
     @addBackground(0, @level.visibleHeight - 157, e, .25)
-    #@addBackground(150, @level.visibleHeight - 150, Crafty.e('2D, Canvas, Color').color(col1).attr({ z: -500, w: (40 * .25) + 1, h: 15 }), .25)
-    #@addBackground(120, @level.visibleHeight - 157, Crafty.e('2D, Canvas, Color').color(col2).attr({ z: -501, w: (50 * .25) + 1, h: 15 }), .25)
-
-    #@addBackground(230, @level.visibleHeight - 150, Crafty.e('2D, Canvas, Color').color(col1).attr({ z: -500, w: (40 * .25) + 1, h: 15 }), .25)
-    #@addBackground(190, @level.visibleHeight - 157, Crafty.e('2D, Canvas, Color').color(col2).attr({ z: -501, w: (50 * .25) + 1, h: 15 }), .25)
   else
     e = Crafty.e('2D, Canvas, Image').image('images/horizon-city.png').attr({ z: -600 })
     @addBackground(0, @level.visibleHeight - 157, e, .25)
-    #@addBackground(0, @level.visibleHeight - 155, Crafty.e('2D, Canvas, Color').color(col3).attr({ z: -600, w: (@delta.x * .25) + 1, h: 30 }), .25)
-    ## This is just for a small impression, this will be replaced by a sprite
-    #@addBackground(90, @level.visibleHeight - 150, Crafty.e('2D, Canvas, Color').color(col1).attr({ z: -500, w: (40 * .25) + 1, h: 15 }), .25)
-    #@addBackground(60, @level.visibleHeight - 162, Crafty.e('2D, Canvas, Color').color(col2).attr({ z: -501, w: (50 * .25) + 1, h: 15 }), .25)
-
-    #@addBackground(170, @level.visibleHeight - 145, Crafty.e('2D, Canvas, Color').color(col1).attr({ z: -500, w: (40 * .25) + 1, h: 15 }), .25)
-    #@addBackground(130, @level.visibleHeight - 157, Crafty.e('2D, Canvas, Color').color(col2).attr({ z: -501, w: (50 * .25) + 1, h: 15 }), .25)
-
-    #@addBackground(230, @level.visibleHeight - 145, Crafty.e('2D, Canvas, Color').color(col1).attr({ z: -500, w: (40 * .25) + 1, h: 15 }), .25)
-    #@addBackground(330, @level.visibleHeight - 157, Crafty.e('2D, Canvas, Color').color(col2).attr({ z: -501, w: (50 * .25) + 1, h: 15 }), .25)

 generator.defineElement 'city', (offset = 0) ->
   # Coastline


backspace improved !

backspace improved !

--- app/views/CNeditor/CNeditor.coffee
+++ app/views/CNeditor/CNeditor.coffee
@@ -1147,7 +1147,7 @@
                         # if lineNext is a Lx, then it must be turned in a Tx
                         if line.lineNext? and line.lineNext.lineType[0]=='L'
                             nextL = line.lineNext
-                            nextL.lineType='T'+nextL.lineType[1]
+                            nextL.lineType = 'T'+nextL.lineType[1]
                             nextL.line$.prop('class',"#{nextL.lineType}-#{nextL.lineDepthAbs}")
                     else
                         isTabAllowed = false
@@ -1294,19 +1294,18 @@
             return linePrevSibling

-    ### ------------------------------------------------------------------------
-    #  _deleteMultiLinesSelections
-    #
-    # Delete the user multi line selection
-    #
-    # prerequisite : at least 2 different lines must be selected
-    # parameters   : startLine = first line to be deleted
-    #                endLine   = last line to be deleted
+
+    ###* ------------------------------------------------------------------------
+     * Delete the user multi line selection
+     * Prerequisite : at least 2 different lines must be selected
+     * If startLine and endLine are specified, lines included between these two
+     * are deleted (including startLine & endLine.
+     * @param  {[line]} startLine [optional] if exists, the whole line will be taken
+     * @param  {[line]} endLine   [optional] if exists, the whole line will be taken
+     * @return {[none]}           [nothing]
     ###
     _deleteMultiLinesSelections : (startLine, endLine) ->
-        # If startLine and endLine are specified, lines included between these
-        # two are removed. This is useful when making line's depth inheritance
-
+
         # true when the caret needs to be repositioned after deletion
         replaceCaret = true

@@ -1359,7 +1358,7 @@
         # 4- append fragment and delete endLine
         # TODO : consider using _insertFrag
         if startLine.line$[0].lastChild.nodeName == 'BR'
-            startLine.line$[0].removeChild( startLine.line$[0].lastChild)
+            startLine.line$[0].removeChild(startLine.line$[0].lastChild)
         startFrag = endOfLineFragment.childNodes[0]
         myEndLine = startLine.line$[0].lastElementChild
         # if startFrag et myEndLine are SPAN and they both have the same class
@@ -1391,7 +1390,7 @@
         line = startLine.lineNext
         if line != null
             deltaDepth1stLine = line.lineDepthAbs - startLineDepthAbs
-            if deltaDepth1stLine >= 1
+            if deltaDepth1stLine > 1
                 while line!= null and line.lineDepthAbs >= endLineDepthAbs
                     newDepth = line.lineDepthAbs - deltaDepth
                     line.lineDepthAbs = newDepth
@@ -1411,10 +1410,10 @@
             firstLineAfterSiblingsOfDeleted = line
             depthSibling = line.lineDepthAbs

-            line = line.linePrev
             while line != null and line.lineDepthAbs > depthSibling
                 line = line.linePrev
-            if line != null
+
+            if line != null and line != firstLineAfterSiblingsOfDeleted
                 prevSiblingType = line.lineType
                 if firstLineAfterSiblingsOfDeleted.lineType!=prevSiblingType
                     if prevSiblingType[1]=='h'
@@ -2807,7 +2806,7 @@
             space = ' '
             while j < l
                 lineElt = lineCode.children().get j
-                if (j+2==l) then space='' #be sure not to insert spaces after BR
+                if (j+2==l) then space= '' #be sure not to insert spaces after BR
                 if lineElt.nodeType == 1 && converter[lineElt.nodeName]?
                     markCode += converter[lineElt.nodeName]($ lineElt) + space
                 else


Stub dashboard api

Stub dashboard api

--- server/api/dashboard.coffee
+++ server/api/dashboard.coffee
@@ -63,6 +63,7 @@
     result["cf"] = (r for r in result["cf"] when r)

 export default dashboard = (registeredUser) ->
+    return {}
     userLists = registeredUser?.adminData?.defaultUserLists
     queries =
         # remember that months start from 0


Not removing modal in IE (em page)

Not removing modal in IE (em page)

--- app/assets/javascripts/em.js.coffee
+++ app/assets/javascripts/em.js.coffee
@@ -49,14 +49,13 @@
     showIntro = ->
         if !window.Sf72.Utils.isMobile()
             startJoyride()
-            if !window.Sf72.Utils.isOldInternetExplorer()
-                $('.em-preview-modal').show()
-                iframe = $("#video-opening")[0]
-                player = $f(iframe)
-                player.addEvent "ready", ->
-                    player.addEvent "finish", (id) ->
-                        $('.tour-intro').hide()
-                        $('.tour-intro-step2').show()
+            $('.em-preview-modal').show()
+            iframe = $("#video-opening")[0]
+            player = $f(iframe)
+            player.addEvent "ready", ->
+                player.addEvent "finish", (id) ->
+                    $('.tour-intro').hide()
+                    $('.tour-intro-step2').show()
         else
             $('.modal-wrapper').hide()


fixed route sending #2

fixed route sending #2

--- app/assets/javascripts/model.coffee
+++ app/assets/javascripts/model.coffee
@@ -784,7 +784,7 @@

 		@initialize().done =>

-			transportBuffer = @buffer
+			transportBuffer = new Float32Array(@buffer.subarray(0, @index))
 			@createBuffer()

 			request(


Improve/fix serializers for update logging (#80)

* Improve/fix serializers for update logging

--- app/coffee/LoggerSerializers.coffee
+++ app/coffee/LoggerSerializers.coffee
@@ -1,10 +1,25 @@
+_ = require('lodash')
+
 showLength = (thing) ->
-	"length: #{thing?.length}"
+	if thing?.length then thing.length else thing
+
+showUpdateLength = (update) ->
+	if update?.op instanceof Array
+		copy = _.cloneDeep(update)
+		copy.op.forEach (element, index) ->
+			copy.op[index].i = element.i.length if element?.i?.length?
+			copy.op[index].d = element.d.length if element?.d?.length?
+			copy.op[index].c = element.c.length if element?.c?.length?
+		copy
+	else
+		update

 module.exports =
 	# replace long values with their length
 	lines: showLength
 	oldLines: showLength
 	newLines: showLength
+	docLines: showLength
+	newDocLines: showLength
 	ranges: showLength
-	update: showLength
+	update: showUpdateLength


US#12: Roles per project

US#12: Roles per project

--- app/coffee/services/resource.coffee
+++ app/coffee/services/resource.coffee
@@ -287,7 +287,8 @@
         return $model.create("memberships", form)

     # Get roles
-    service.getRoles = -> queryMany('roles')
+    service.getRoles = (projectId) ->
+        return queryMany('roles', {project: projectId})

     # Get a milestone lines for a project.
     service.getMilestones = (projectId) ->


Only show the first row of artworks and disable Show More.

--- apps/following/client/index.coffee
+++ apps/following/client/index.coffee
@@ -41,18 +41,20 @@
         $parent.append $container
       $followButton = $container.find(".follow-button")
       $artworks = $container.find('.artworks')
-      new FillwidthView(
+      view = new FillwidthView
         artworkCollection: @artworkCollection
         fetchOptions: { 'filter[]': 'for_sale' }
         collection: artworks
-        seeMore: true
         empty: (-> @$el.parent().remove() )
         el: $artworks
-      ).nextPage(false, 10)
+      view.render()
       new FollowButton
         followItemCollection: @followItemCollection
         model: item
         el: $followButton
+      _.defer ->
+        view.hideFirstRow()
+        view.removeHiddenItems()

   _loadNextPage: ->
     end = @itemsPerPage * @pageNum


We don't need the session to extend the registration.

--- app/javascripts/registration.coffee
+++ app/javascripts/registration.coffee
@@ -9,11 +9,10 @@
     # * Adds the title and session virtual properties
     #
     # @param registration the registration to extend
-    # @param session the parent session object
     # @param scan the source scan
     # @param number the one-based registration number within the scan
     # @returns the extended registration object
-    extend: (registration, session, scan, number) ->
+    extend: (registration, scan, number) ->
       # Add the general image sequence properties.
       ImageSequence.extend(registration)
       # Add the parent scan reference.


Remove random IE9 hack

Remove random IE9 hack

--- app/lib/surface/LayerAdapter.coffee
+++ app/lib/surface/LayerAdapter.coffee
@@ -510,15 +510,10 @@
     unless thangType.rasterImage
       console.error("Cannot render the LayerAdapter SpriteSheet until the raster image for <#{thangType.get('name')}> is loaded.")

-    # hack for IE9, otherwise width/height are not set
-    $img = $(thangType.rasterImage[0])
-    $('body').append($img)
-
     bm = new createjs.Bitmap(thangType.rasterImage[0])
     scale = thangType.get('scale') or 1
     frame = spriteSheetBuilder.addFrame(bm, null, scale)
     spriteSheetBuilder.addAnimation(@renderGroupingKey(thangType), [frame], false)
-    $img.remove()

   #- Distributing new Segmented/Singular/RasterSprites to Lanks


Fix internal links for comments

Fix internal links for comments

--- app/pages/profile/comment-link.cjsx
+++ app/pages/profile/comment-link.cjsx
@@ -3,6 +3,7 @@
 apiClient = require 'panoptes-client/lib/api-client'
 talkClient = require 'panoptes-client/lib/talk-client'
 {Markdown} = (require 'markdownz').default
+{Link} = require 'react-router'

 module.exports = React.createClass
   displayName: 'CommentLink'
@@ -46,7 +47,7 @@
           Promise.resolve "/talk/#{board.id}/#{discussion.id}?comment=#{comment.id}"
         href.then (href) =>
           @setState
-            href: window.location.origin + href
+            href: href

   render: ->

@@ -57,7 +58,7 @@
         {if @state.board?.id and @state.discussion?.id

             {' '}in{' '}
-
+            <Link to={@state.href}>
               {if @state.boardProject? and @state.owner? and !@props.project?

                   {@state.boardProject.display_name}
@@ -66,7 +67,7 @@
               {@state.board?.title}
               {' '}➞{' '}
               {@state.discussion?.title}
-
+            </Link>
           }


Update hourOfCodeOptions property name

Update hourOfCodeOptions property name


Make isSupported a class method

Make isSupported a class method

--- app/scripts/lib/bounce/index.coffee
+++ app/scripts/lib/bounce/index.coffee
@@ -51,20 +51,6 @@

     prefixes

-  isSupported: ->
-    style = document.createElement("dummy").style
-
-    propertyLists = [
-      ["transform", "webkitTransform"],
-      ["animation", "webkitAnimation"]
-    ]
-    for propertyList in propertyLists
-      propertyIsSupported = false
-      for property in propertyList
-        propertyIsSupported ||= property of style
-
-      return false unless propertyIsSupported
-
   getKeyframeCSS: (options = {}) ->
     @name = options.name or Bounce.generateName()

@@ -98,4 +84,19 @@
   @generateName: ->
     "animation-#{Bounce.counter++}"

+
+  @isSupported: ->
+    style = document.createElement("dummy").style
+
+    propertyLists = [
+      ["transform", "webkitTransform"],
+      ["animation", "webkitAnimation"]
+    ]
+    for propertyList in propertyLists
+      propertyIsSupported = false
+      for property in propertyList
+        propertyIsSupported ||= property of style
+
+      return false unless propertyIsSupported
+
 module.exports = Bounce


Add debian to dev config

Add debian to dev config

--- src/web/js/coffee/ConfigServiceController.coffee
+++ src/web/js/coffee/ConfigServiceController.coffee
@@ -39,7 +39,7 @@

 		@hostConfs = {};
 		@hostConfs['localhost'] = @loadManuConf;
-		@hostConfs['archlinux'] = @loadLocalhostConf;
+		@hostConfs['debian'] = @loadLocalhostConf;
 		@hostConfs['dj.yayo.fr'] = @loadProdConf;
 		if @hostConfs[host]? then @hostConfs[host]();

@@ -48,8 +48,8 @@
 		@config['webservice']['url'] = 'http://localhost:4545'

 	loadLocalhostConf: () =>
-		@config['website']['url'] = 'http://archlinux:8000'
-		@config['webservice']['url'] = 'http://archlinux:4545'
+		@config['website']['url'] = 'http://debian:8000'
+		@config['webservice']['url'] = 'http://debian:4545'

 	loadProdConf: () =>
 		@config['website']['url'] = 'http://dj.yayo.fr'


upated tests. all unit tests pass

upated tests. all unit tests pass

--- tests/unit/preprocess.test.coffee
+++ tests/unit/preprocess.test.coffee
@@ -41,31 +41,20 @@
         payload = {
           account: {
             friends: [
-              { name: 'jay', nicknames: [ {name: 'gus', giver: [ { name: 'flip' }, { name: 'gus' } ] } ] },
-              { name: 'lou', nicknames: [{name: 1, giver: 1}] }
+              { name: 'jay', nicknames: [ {name: 'gus', giver: [{name: 'flip'}, {name: 'gus'}] } ] },
+              { name: 'lou' }
             ]
           }
         }
-        payload = preprocess._preprocessPayload(payload, schemaArrayOfObjects)
-        console.log(99112, payload.errors);
-        console.log(99, payload.payload.account.friends[0]);
-        console.log(99, payload.payload.account.friends[1]);
-        throw new Error()
-
-      it 'should validate, transform, and reconstruct a payload for the values schema', ->
-        schemaValues = _.clone(schemaValues)
-        payload  = {
+        result = preprocess._preprocessPayload(payload, schemaArrayOfObjects)
+        result.payload.should.eql({
           account: {
-            name: 'jay',
-            'friends': ['gab', 'lou']
+            friends: [
+              { name: 'jay!', nicknames: [ {name: 'gus', giver: [{name: 'flip'}, {name: 'gus'}] } ] },
+              { name: 'lou!', nicknames: [] }
+            ]
           }
-          newsletter: false
-        }
-        deconstructedPayload = preprocess._deconstructPayload(payload)
-        schema = schema._preprocessSchema(schemaValues)
-        payload = preprocess._preprocessPayload(deconstructedPayload, schema)
-        result = preprocess._reconstructPayload(payload.payload);
-        throw new Error()
+        });

   describe '_deconstructPayload', ->
     it 'should parse an insert payload', ->


Keyboard control, support paging between exercises

--- exercises/src/components/preview/controls.cjsx
+++ exercises/src/components/preview/controls.cjsx
@@ -1,43 +1,98 @@
 React = require 'react'
 _ = require 'underscore'
 BS = require 'react-bootstrap'
+keymaster = require 'keymaster'

 {ExerciseActions, ExerciseStore} = require 'stores/exercise'
+
+KEYBINDING_SCOPE = 'ex-preview'

 PreviewControls = React.createClass
   propTypes:
     id:   React.PropTypes.string
     location: React.PropTypes.object

-  goToVersion: (version, ev) ->
-    ev.preventDefault()
+  componentDidMount: ->
+    # left/right to go between exercises, and up/down to go between versions.
+    keymaster('up',    KEYBINDING_SCOPE, @goNextVersion)
+    keymaster('down',  KEYBINDING_SCOPE, @goPrevVersion)
+    keymaster('left',  KEYBINDING_SCOPE, @goPrevExercise)
+    keymaster('right', KEYBINDING_SCOPE, @goNextExercise)
+    keymaster.setScope(KEYBINDING_SCOPE)
+
+  disableKeys: ->
+    keymaster.deleteScope(KEYBINDING_SCOPE)
+
+  goNextExercise: ->
     exercise = ExerciseStore.get(@props.id)
-    @props.location.visitPreview("#{exercise.number}@#{version}")
+    @props.location.visitPreview("#{exercise.number+1}")

+  goPrevExercise: ->
+    exercise = ExerciseStore.get(@props.id)
+    @props.location.visitPreview("#{exercise.number-1}")
+
+  goNextVersion: ->
+    exercise = ExerciseStore.get(@props.id)
+    version = @findNextVersion()
+    @props.location.visitPreview("#{exercise.number}@#{version}") if version?
+
+  goPrevVersion: ->
+    exercise = ExerciseStore.get(@props.id)
+    version = @findPrevVersion()
+    @props.location.visitPreview("#{exercise.number}@#{version}") if version?
+
+  findNextVersion: ->
+    exercise = ExerciseStore.get(@props.id)
+    return null unless exercise
+    versions = _.sortBy(exercise.versions, _.partial(parseInt, _, 10))
+    _.find(versions, (version) -> version > exercise.version)
+
+  findPrevVersion: ->
+    exercise = ExerciseStore.get(@props.id)
+    return null unless exercise
+    versions = _.sortBy(exercise.versions, _.partial(parseInt, _, 10)).reverse()
+    _.find(versions, (version) -> version < exercise.version)

   render: ->
     exercise = ExerciseStore.get(@props.id)
     return null unless exercise?

-    versions = _.sortBy(exercise.versions, parseInt)
+    nextVersion = @findNextVersion()
+    prevVersion = @findPrevVersion()

-    nextVersion = _.find(versions, (version) -> version > exercise.version)
-    prevVersion = _.find(versions.reverse(), (version) -> version < exercise.version)
+    # left/right to go between exercises, and up/down to go between versions.

       <BS.ButtonGroup className="paging-controls">
+
         <BS.Button href="#"
-          onClick={_.partial(@goToVersion, prevVersion)}
+          onClick={@goPrevExercise}
+          title="Go to previous exercise"
+          disabled={exercise.number is 1}
+        >{if exercise.number is 1 then '◅' else '◀' }</BS.Button>
+
+        <BS.Button href="#"
+          onClick={@goPrevVersion}
           disabled={not prevVersion}
-        >{if prevVersion then '⇦' else 'X' }</BS.Button>
+          title="Go down a version"
+        >
+          {if prevVersion then '▼' else '▽' }
+        </BS.Button>

         Viewing exercise {exercise?.uid}

         <BS.Button href="#"
-          onClick={_.partial(@goToVersion, nextVersion)}
+          onClick={@goNextVersion}
           disabled={not nextVersion}
-        >{if nextVersion then '⇨' else 'X'}</BS.Button>
+          title="Go up a version"
+        >{if nextVersion then '▲' else '△'}</BS.Button>
+
+        <BS.Button href="#"
+          onClick={@goNextExercise}
+          title="Go to next exercise"
+        >►</BS.Button>
+
       </BS.ButtonGroup>


landing: update default features navigation link

--- client/landing/site.landing/coffee/core/topnavigation.coffee
+++ client/landing/site.landing/coffee/core/topnavigation.coffee
@@ -5,11 +5,11 @@
 module.exports = class TopNavigation extends kd.CustomHTMLView

   menu = [
-    { title : 'Koding University', href : 'http://learn.koding.com',  name : 'about' }
-    { title : 'Teams',             href : '/Teams',                   name : 'teams' }
-    { title : 'Features',          href : '/Features',                name : 'features' }
-    { title : 'Sign In',           href : '/Login',                   name : 'buttonized white login',  attributes : testpath : 'login-link' }
-    { title : 'Sign Up',           href : '/Register',                name : 'buttonized green signup', attributes : testpath : 'signup-link' }
+    { title : 'Koding University', href : 'http://learn.koding.com',     name : 'about' }
+    { title : 'Teams',             href : '/Teams',                      name : 'teams' }
+    { title : 'Features',          href : 'https://koding.com/Features', name : 'features', attributes: target: '_blank' }
+    { title : 'Sign In',           href : '/Login',                      name : 'buttonized white login',  attributes : testpath : 'login-link' }
+    { title : 'Sign Up',           href : '/Register',                   name : 'buttonized green signup', attributes : testpath : 'signup-link' }
   ]

   constructor: (options = {}, data) ->


Updates golumn to send well-formed parameters

--- app/assets/javascripts/backend/animals.js.coffee
+++ app/assets/javascripts/backend/animals.js.coffee
@@ -53,13 +53,17 @@
       # by default, build url to move animals
       @rebuildUrl = =>
         options = Array.from(arguments).shift()
-        options['animals_ids'] ||= Object.keys(@selectedItemsIndex)
+        options['targets_attributes'] ||= []
+        options['targets_attributes'] = ko.utils.arrayMap Object.keys(@selectedItemsIndex), (id) ->
+          { product_id: id, reference_name: 'animal', new_container_id: options['container'], new_group_id: options['group'] }

         parameters = options['parameters'] || false
         base_url = options['base_url'] || $('a[data-target=animal_group_changing]').attr('href')

         delete options['base_url']
         delete options['parameters']
+        delete options['container']
+        delete options['group']

         base_url += "&#{$.param(options)}" if parameters


Rescue around invalid page id's for push state

--- app/assets/javascripts/controllers/editor.coffee
+++ app/assets/javascripts/controllers/editor.coffee
@@ -10,9 +10,9 @@

 class CMSimple.Editor extends Spine.Controller
   el: 'body'
-  constructor: (@current_page)->
+  constructor: (page)->
     super()
-    @setCurrentPage(@current_page)
+    @setCurrentPage(page)
     @initializeMercury()

     @routes
@@ -26,6 +26,7 @@
     Spine.Route.setup(history: true)

   setCurrentPage: (page)->
+    return unless page
     @current_page.unbind() if @current_page
     @current_page = page
     CMSimple.Editor.current_page = @current_page


Interpolate lines on load charts using step-after

Draws each change as "immediate", rather than a smooth change from the
previous data-point.

Closes #72

--- app/assets/javascripts/testing_grounds.js.coffee
+++ app/assets/javascripts/testing_grounds.js.coffee
@@ -221,6 +221,12 @@
             y: series.values[0].y
           )

+    for datum in data
+      # Add an extra data point to make the "step-after" smoothing fit better
+      # (otherwise it appears that the last frame is not present).
+      last = datum.values[datum.values.length - 1]
+      datum.values.push(x: last.x + 1, y: last.y)
+
     nv.addGraph =>
       d3.select(intoSelector).datum(data).call(@chart())

@@ -275,6 +281,8 @@
     chart.lines.duration(0)
     chart.lines2.duration(0)
     chart.lines2.forceY([0.0])
+
+    chart.lines.interpolate('step-after')

     chart.xAxis
          .tickFormat(@formatDateFromFrame)


social: invitations: pass forgotten role info

--- workers/social/lib/social/traits/joinable.coffee
+++ workers/social/lib/social/traits/joinable.coffee
@@ -33,7 +33,7 @@
       else unless invite?
         callback new KodingError 'Invalid invitation code.'
       else
-        addToGroup_.call this, (err)->
+        addToGroup_.call this, client, { as }, (err) ->
           if err then callback err
           else invite.accept delegate, (err) -> callback err


Fix one more problem with folds

Fix one more problem with folds

--- assets/scripts/app/views/job.coffee
+++ assets/scripts/app/views/job.coffee
@@ -216,12 +216,14 @@
             fragment.appendChild(line)

           if payload.openFold
-            folds = fragment.querySelectorAll(".fold.#{payload.fold}")
+            folds = fragment.querySelectorAll(".fold.#{payload.openFold}")
+            console.log folds
             if fold = folds[folds.length - 1]
               fold = $(fold)
             else
-              fold = this.$(".fold.#{payload.fold}:last")
-
+              fold = this.$(".fold.#{payload.openFold}:last")
+
+            console.log fold, payload
             fold.removeClass('show-first-line').addClass('open')

           if payload.foldEnd


dont need handleAjaxError anymore

dont need handleAjaxError anymore

--- SingularityUI/app/views/tail.coffee
+++ SingularityUI/app/views/tail.coffee
@@ -225,17 +225,5 @@
         @$('.line').removeClass('highlightLine')
         $(e.currentTarget).closest('.line').addClass('highlightLine')

-    # If we get a 400 we can render a nicer
-    # message that the file is being generated
-    handleAjaxError: (response) =>
-        @ajaxError =
-            status: true
-            errorType: response.errorType
-
-        @dataInterval = setInterval =>
-            @collection.fetchInitialData()
-        , 2000
-
-        @render()

 module.exports = TailView


Workspace: Fixed opened editor paths in tabbed editors.

--- client/landing/app/CommonViews/workspace/panes/collaborativetabbededitorpane.coffee
+++ client/landing/app/CommonViews/workspace/panes/collaborativetabbededitorpane.coffee
@@ -75,12 +75,10 @@
       @activeTabIndex = newIndex

   createEditorInstance: (file, content, sessionKey) ->
-    if file
-      fileIndexInOpenedFiles = @openedFiles.indexOf(file.path)
-      if fileIndexInOpenedFiles > -1
-        return  @tabView.showPaneByIndex fileIndexInOpenedFiles
-    else
-      file = FSHelper.createFileFromPath "localfile:/untitled.txt"
+    file      = FSHelper.createFileFromPath "localfile:/untitled.txt"  unless file
+    plainPath = FSHelper.plainPath file.path
+    index     = @openedFiles.indexOf plainPath
+    return @tabView.showPaneByIndex index  if index > -1

     pane   = new KDTabPaneView
       name : file.name
@@ -104,9 +102,8 @@
     workspaceRefData =
       sessionKey : editor.sessionKey

-    if file
-      workspaceRefData.path = file.path
-      @openedFiles.push file.path
+    workspaceRefData.path = plainPath
+    @openedFiles.push plainPath

     @tabsRef.push workspaceRefData  unless sessionKey

@@ -121,7 +118,7 @@
           delete tabs[key]
         @workspaceRef.set { tabs }

-      @openedFiles.splice @openedFiles.indexOf(file.path), 1
+      @openedFiles.splice @openedFiles.indexOf(plainPath), 1

     return yes # return something instead of workspaceRef.child


don't publish status for users in completed experiments

--- admin/admin.coffee
+++ admin/admin.coffee
@@ -18,7 +18,6 @@
   ]

 userFindOptions =
-  # {"status.online": true},
   fields:
     status: 1
     turkserver: 1
@@ -31,6 +30,13 @@

   return Meteor.users.find {}, userFindOptions

+# Don't return status here as the user is not connected to this experiment
+offlineFindOptions =
+  fields:
+    turkserver: 1
+    username: 1
+    workerId: 1
+
 # Helper publish function to get users for experiments that have ended.
 # Necessary to watch completed experiments.
 Meteor.publish "tsGroupUsers", (groupId) ->
@@ -40,7 +46,7 @@

   # This won't update if users changes, but it shouldn't after an experiment is completed
   # TODO Just return everything here; we don't know what the app subscription was using
-  subHandle = Meteor.users.find({ _id: $in: exp.users}, userFindOptions).observeChanges
+  subHandle = Meteor.users.find({ _id: $in: exp.users}, offlineFindOptions).observeChanges
     added: (id, fields) ->
       sub.added "users", id, fields
     changed: (id, fields) ->


tests for fair overview

tests for fair overview

--- apps/fair/test/templates.coffee
+++ apps/fair/test/templates.coffee
@@ -8,9 +8,14 @@
 { fabricate }   = require 'antigravity'
 Fair            = require '../../../models/fair'
 Profile         = require '../../../models/profile'
+CoverImage      = require '../../../models/cover_image'
 SearchResult    = require '../../../models/search_result'
 Artists         = require '../../../collections/artists'
 Partners        = require '../../../collections/partners'
+Item            = require '../../../models/item'
+Items           = require '../../../collections/items'
+OrderedSet      = require '../../../models/ordered_set'
+OrderedSets     = require '../../../collections/ordered_sets'
 cheerio         = require 'cheerio'

 render = (templateName) ->
@@ -164,8 +169,54 @@

   describe 'overview', ->
     before ->
-      fair = new Fair (fabricate 'fair', about: 'about the fair', image_versions: ['wide'], image_url: "foo/wide.jpg")
+      fair = new Fair (fabricate 'fair', about: 'about the fair')
+      coverImage = new CoverImage(image_versions: ['wide'], image_url: "foo/wide.jpg")
       profile = new Profile (fabricate 'fair_profile')
+      primarySets = new OrderedSets()
+
+      # Explore ordered set
+      editorial = new OrderedSet fabricate('set', {
+        key: "editorial"
+        item_type: "FeaturedLink"
+      })
+      editorialItems = new Items(null, 'editorial-foo')
+      editorialItems.add new Item( fabricate 'featured_link', { title: 'Chinese Art' } )
+      editorialItems.add new Item( fabricate 'featured_link', { title: 'Moar Chinese Art' } )
+      editorial.set items: editorialItems
+      primarySets.add editorial
+
+      # Explore ordered set
+      explore = new OrderedSet fabricate('set', {
+        key: "explore"
+        item_type: "FeaturedLink"
+      })
+      exploreItems = new Items(null, 'explore-foo')
+      exploreItems.add new Item( fabricate 'featured_link', { title: 'Explore Art' } )
+      exploreItems.add new Item( fabricate 'featured_link', { title: 'Moar Explore Art' } )
+      explore.set items: exploreItems
+      primarySets.add explore
+
+      # Primary ordered set
+      primary = new OrderedSet fabricate('set', {
+        key: "primary"
+        item_type: "FeaturedLink"
+      })
+      primaryItems = new Items(null, 'primary-foo')
+      primaryItems.add new Item( fabricate 'featured_link', { title: 'Primary Art' } )
+      primaryItems.add new Item( fabricate 'featured_link', { title: 'Moar Primary Art' } )
+      primary.set items: primaryItems
+      primarySets.add primary
+
+      # Curator ordered set
+      curator = new OrderedSet fabricate('set', {
+        key: "curator"
+        item_type: "FeaturedLink"
+      })
+      curatorItems = new Items(null, 'curator-foo')
+      curatorItems.add new Item( fabricate 'featured_link', { title: 'Curator Art' } )
+      curatorItems.add new Item( fabricate 'featured_link', { title: 'Moar Curator Art' } )
+      curator.set items: curatorItems
+      primarySets.add curator

       filteredSearchOptions = new Backbone.Model {
         related_gene:
@@ -189,6 +240,8 @@
         fair: fair
         profile: profile
         filteredSearchColumns: filteredSearchColumns
+        coverImage: coverImage
+        primarySets: primarySets

     it 'renders without errors', ->
       $ = cheerio.load @template
@@ -196,3 +249,9 @@
       $('.fair-search-options-column a').length.should.equal 10
       $('.fair-search-options-column').text().should.include 'contemporary pop'
       $('.feature-image').length.should.equal 1
+
+      $('.container-right .small-section').length.should.equal 2
+      $('.container-left .small-section').length.should.equal 2
+      $('.fair-overview-curator .small-section').length.should.equal 2
+      $('.fair-overview-post-container .large-post').length.should.equal 1
+      $('.fair-overview-post-container .small-post').length.should.equal 1


Making comments prettier in the output JS

--- source/coffee/generator.coffee
+++ source/coffee/generator.coffee
@@ -111,7 +111,7 @@
     rv = ''
     for comment in @ts.comments when comment.line <= @line and not comment.written
       comment.written = yes
-      rv += i + '/*' + comment.value + '*/'
+      rv += i + '/*' + comment.value + '*/\n'
     rv += i + @statement.js()
     return rv


In removeDuplicateSubmits, do not remove submit if duplicate is DP

--- server/models/submit.coffee
+++ server/models/submit.coffee
@@ -63,6 +63,8 @@
     if @comments.length > 0
         return false
     if @outcome == "AC" or @outcome == "IG" or @outcome == "DQ"
+        return false
+    if other.outcome == "DP"
         return false
     if @force
         return false


added hasMod to js block

added hasMod to js block

--- app/assets/javascripts/block.coffee
+++ app/assets/javascripts/block.coffee
@@ -45,6 +45,11 @@

 	delMod: (mod, value) ->
 		@$node.delMod(mod, value)
+
+	hasMod: (mod, value) ->
+		mod = $$.makeMod(mod, value)
+		@$node.hasClass("b-#{@name}--#{mod}")
+
 # class window.Block
 # 	constructor: ($b) ->
 # 		name = $b.getBlockName()


correctly solve suicide move bug

correctly solve suicide move bug

--- app/assets/javascripts/board.coffee
+++ app/assets/javascripts/board.coffee
@@ -113,16 +113,10 @@
         @dots_checked = []
         if @is_alive dot
           dot.check_nearby_dots true
-          rc = true
         else
           dot.check_nearby_dots false
           if @captured_color is dot.owner or @captured_color is 'e'
             @to_be_captured[@to_be_captured.length] = dot
-          rc = false
-          if @color_in_turn is 'w'
-            @color_in_turn = 'b'
-          else
-            @color_in_turn = 'w'

         @capture_stones()
         @captured_color = 'e'
@@ -137,6 +131,7 @@
           target.removeClass('last_text')

         @draw_last_mark dot, draw_flag
+        rc = true
         return rc

     return rc
@@ -532,24 +527,19 @@

   player.board.draw_fake_stone dot
   window.pendding_move = ->
-    if player.board.on_dot_click(dot) is true
-      move[dot.owner.toUpperCase()] = dot.name
-      player.parser.update_game move
-
-      if black_player is current_player
-        $('#game').attr('current_player', white_player)
-      else
-        $('#game').attr('current_player', black_player)
-      # for better user experience
-      $('#pass').hide()
-      $('#score').hide()
-      $('#resign').hide()
-      $.post(window.location.pathname  + '/moves', {"sgf":$("#game").attr("sgf"), "player_id":$("#game").attr("current_user")})
+    player.board.on_dot_click(dot)
+    move[player.board.color_in_turn.toUpperCase()] = dot.name
+    player.parser.update_game move
+
+    if black_player is current_player
+      $('#game').attr('current_player', white_player)
     else
-      if window.get_locale() is 'zh'
-        alert('无效的落子点.')
-      else
-        alert('This is an illegal move.')
+      $('#game').attr('current_player', black_player)
+    # for better user experience
+    $('#pass').hide()
+    $('#score').hide()
+    $('#resign').hide()
+    $.post(window.location.pathname  + '/moves', {"sgf":$("#game").attr("sgf"), "player_id":$("#game").attr("current_user")})

 window.post_comments = ->
   (e) ->


Small style improvements in NavBarView

Small style improvements in NavBarView

--- app/coffeescripts/NavBarView.coffee
+++ app/coffeescripts/NavBarView.coffee
@@ -9,18 +9,19 @@
     @render()
     for key, values of args.menuItems
       keyLower = $a.Util.toLowerCaseAndDashed(key)
-      attrs = {
+      attrs =
         text: key
         textLower: keyLower
         attach: ".#{@className}"
-      }
+
       new $a.NavParentItemView(attrs)
       for subkey, event of values
-        attrs = {
+        attrs =
           text: subkey
           textLower: $a.Util.toLowerCaseAndDashed(subkey)
-          event: event, attach: keyLower
-        }
+          event: event
+          attach: keyLower
+
         new $a.NavChildItemView(attrs)

   render: ->


Remove obsolete code

Remove obsolete code

--- services/web/public/coffee/ide/binary-files/controllers/BinaryFileController.coffee
+++ services/web/public/coffee/ide/binary-files/controllers/BinaryFileController.coffee
@@ -57,7 +57,6 @@
 							ide.binaryFilesManager.openFileById(new_file_id)
 						, 1000
 					)
-					# loadTextFileFilePreview()
 				.finally () ->
 					$scope.refreshing = false


Fix app thumb views tooltip offsets

Fix app thumb views tooltip offsets

--- client/app/Applications/StartTab.kdapplication/views/appthumbview.coffee
+++ client/app/Applications/StartTab.kdapplication/views/appthumbview.coffee
@@ -50,6 +50,9 @@
       cssClass : "icon compile"
       tooltip  :
         title  : "Click to compile"
+        offset :
+          top  : 4
+          left : -5
       click    : =>
         @showLoader()
         @getSingleton("kodingAppsController").compileApp manifest.name, (err)=>
@@ -60,6 +63,9 @@
       tagName  : "span"
       cssClass : "icon info"
       tooltip  :
+        offset :
+          top  : 4
+          left : -5
         title  : """

             #{name} #{version} by #{author}
@@ -74,6 +80,9 @@
       cssClass : "icon delete"
       tooltip  :
         title  : "Click to delete"
+        offset :
+          top  : 4
+          left : -5
       click    : =>
         @delete.hideTooltip()
         @deleteModal = new KDModalView


pointless commit

pointless commit

--- client/landing/app/Applications/Shell.kdapplication/AppController.coffee
+++ client/landing/app/Applications/Shell.kdapplication/AppController.coffee
@@ -47,6 +47,7 @@
       for diff in (item while (item = _orderedMessages[i++])?)
         # console.log "updating screen with:",diff
         currentScreen = (@dmp.patch_apply diff,@lastScreen)[0]
+        # currentScreen = diff
         @getView().updateScreen(currentScreen)
         @lastScreen = currentScreen
         @_lastMessageProcessed = i-1


Use the right path to fixture

Use the right path to fixture

--- spec/CanvasToBuffer.coffee
+++ spec/CanvasToBuffer.coffee
@@ -38,7 +38,7 @@
           chai.expect(data).to.be.an 'object'
           chai.expect(data).to.be.an.instanceOf Buffer
           done()
-        src = 'spec/test-80x80.jpg'
+        src = 'original.jpg'
         testutils.getCanvasWithImageNoShift src, (canvas) ->
           ins.beginGroup 'foo'
           ins.send canvas


Added a spec for performing a drag-less click on a non-handle area of a draggable.

--- spec/draggable_spec.coffee
+++ spec/draggable_spec.coffee
@@ -203,6 +203,21 @@
             expect(@end.top - @start.top).toBe(0)
             expect(@end.left - @start.left).toBe(0)

+        describe 'after having something other than its handle clicked without having been dragged', ->
+
+          beforeEach ->
+            # Spy on clicks made on the draggable
+            spyOnEvent(@$draggable, 'click')
+
+            # Click the draggable, but don't move it
+            @$draggable
+              .simulate('mousedown')
+              .simulate('mouseup')
+              .simulate('click')
+
+          it 'should receive the click event', ->
+            expect('click').toHaveBeenTriggeredOn(@$draggable)
+
   describe 'configured with a helper', ->

     describe 'such as the ‘clone’ helper', ->


add refactored scale_zoom

add refactored scale_zoom

--- bokehjs/src/coffee/util/zoom.coffee
+++ bokehjs/src/coffee/util/zoom.coffee
@@ -1,12 +1,23 @@
-scale_range = ({frame, factor, center, h_axis_only = false, v_axis_only = false}) ->
-  # Util for Tools to Scale ranges of a frame for use by PlotCanvasView.update_range()
-  #
-  # Parameters:
-  # * frame
-  # * factor      - scaling factor (clamped: -0.9 < args.factor < 0.9)
-  # * center      - center point for scaling
-  # * v_axis_only - if true only scale the vertical axis
-  # * h_axis_only - if true only scale the horizontal axis
+# Module for zoom-related functions
+
+scale_range = (frame, factor, h_axis=true, v_axis=true, center=null) ->
+  """
+  Utility function for zoom tools to calculate/create the zoom_info object
+  of the form required by ``PlotCanvasView.update_range``
+
+  Parameters:
+    frame : CartesianFrame
+    factor : Number
+    center : object, optional
+      of form {'x': Number, 'y', Number}
+    h_axis : Boolean, optional
+      whether to zoom the horizontal axis (default = true)
+    v_axis : Boolean, optional
+      whether to zoom the horizontal axis (default = true)
+
+  Returns:
+    object:
+  """

   hr = frame.h_range
   vr = frame.v_range
@@ -17,35 +28,25 @@
   else if factor < -0.9
     factor = -0.9

-  vx_low  = hr.start
-  vx_high = hr.end
+  [vx_low, vx_high] = [hr.start, hr.end]
+  [vy_low, vy_high] = [vr.start, vr.end]

-  vy_low  = vr.start
-  vy_high = vr.end
-
-  frame_center = {
-    x: (vx_high + vx_low) / 2.0
-    y: (vy_high + vy_low) / 2.0
-  }
-
-  # if center or either dimension center.x,y
-  # isn't given, use the frame_center
-  vx = center?.x ? frame_center.x
-  vy = center?.y ? frame_center.y
+  vx = if center? then center.x else (vx_high + vx_low) / 2.0
+  vy = if center? then center.y else (vy_high + vy_low) / 2.0

   if vx < vx_low or vx > vx_high
-    v_axis_only = true
+    h_axis = false
   if vy < vy_low or vy > vy_high
-    h_axis_only = true
+    v_axis = false

-  if not v_axis_only
+  if h_axis
     sx0 = vx_low  - (vx_low  - vx)*factor
     sx1 = vx_high - (vx_high - vx)*factor
   else
     sx0 = vx_low
     sx1 = vx_high

-  if not h_axis_only
+  if v_axis
     sy0 = vy_low  - (vy_low  - vy)*factor
     sy1 = vy_high - (vy_high - vy)*factor
   else
@@ -54,12 +55,12 @@

   xrs = {}
   for name, mapper of frame.x_mappers
-    [start, end] = mapper.v_map_from_target([sx0, sx1], true)
+    [start, end] = mapper.v_map_from_target([sx0, sx1])
     xrs[name] = {start: start, end: end}

   yrs = {}
   for name, mapper of frame.y_mappers
-    [start, end] = mapper.v_map_from_target([sy0, sy1], true)
+    [start, end] = mapper.v_map_from_target([sy0, sy1])
     yrs[name] = {start: start, end: end}

   # OK this sucks we can't set factor independently in each direction. It is used


add initial tests for image_url model

add initial tests for image_url model

--- bokehjs/test/test_renderer/test_image_url.coffee
+++ bokehjs/test/test_renderer/test_image_url.coffee
@@ -1,14 +1,19 @@
 {expect} = require "chai"
 utils = require "../utils"
-jsdom = require 'mocha-jsdom'
 sinon = require 'sinon'

 base = utils.require "common/base"
 {Collections} = base

-describe "image_url module", ->
+describe "image_url renderer", ->
   describe "default creation", ->
-    i = Collections('ImageURL').create()
+    r = Collections('ImageURL').create()

     it "should have global_alpha=1.0", ->
       expect(r.get('global_alpha')).to.be.equal 1.0
+
+    it "should have retry_attempts=0", ->
+      expect(r.get('retry_attempts')).to.be.equal 0
+
+    it "should have retry_timeout=0", ->
+      expect(r.get('retry_timeout')).to.be.equal 0


Update model names (matches filename now)

--- api.coffee
+++ api.coffee
@@ -5,11 +5,11 @@
 bus = require './bus'

 models = require('./db').models
-User = models.User
-Competition = models.Competition
-Vote = models.Vote
-CompetitionMembership = models.CompetitionMembership
-CompetitionCandidate = models.CompetitionCandidate
+User = models.user
+Competition = models.competition
+Vote = models.vote
+CompetitionMembership = models.competitionMembership
+CompetitionCandidate = models.competitionCandidate

 # Provides a set of methods for voting data CRUD operations
 class API


Add missing role translations, closes #338

--- app/pages/project/science-case.cjsx
+++ app/pages/project/science-case.cjsx
@@ -11,6 +11,9 @@
     owner: 'Owner'
     collaborator: 'Collaborator'
     translator: 'Translator'
+    scientist: 'Scientist'
+    moderator: 'Moderator'
+    tester: 'Tester'

 module.exports = React.createClass
   displayName: 'ProjectScienceCasePage'


remove .getDOMNode()

remove .getDOMNode()

--- views/akashic-resource-chart.cjsx
+++ views/akashic-resource-chart.cjsx
@@ -325,7 +325,7 @@
           )(@showData)}]

   renderChart: ->
-    node = @refs.chart.getDOMNode()
+    node = @refs.chart
     if @sleepMode
       theme = dark
     else


Check for Windows admin differently

Check for Windows admin differently

--- app.coffee
+++ app.coffee
@@ -54,17 +54,17 @@
 #      MAIN
 #################

-# We check if were running on a Windows system.
-# If so, check for admin. If no admin, warn and close.
+# We check if we can write to directory.
+# If no admin and is required, warn and close.
 isWindowsAdmin = (cb) ->
   if process.platform != 'darwin'
-    exec 'NET SESSION', (err, std, ste) ->
-      if ste.length != 0
+    fs.writeFile GLOBAL.lolInstallPath + '/test.txt', 'Testing Write', (err) ->
+      if err or !fs.existsSync(GLOBAL.lolInstallPath + '/test.txt')
         cl 'Whoops! You need to run me as an admin. Right click on my file and hit "Run as Administrator"', 'yellow'
         enterToExit()
       else
+        fs.unlinkSync(GLOBAL.lolInstallPath + '/test.txt')
         cb null
-
   else
     cb null

@@ -342,10 +342,10 @@

 async.waterfall [
-  isWindowsAdmin
   checkVer
   getInstallPath
   clInstallPath
+  isWindowsAdmin
   getRiotVer
   getChamps
   processChamps


Add eye offsets and fix a thing

Add eye offsets and fix a thing

--- app.coffee
+++ app.coffee
@@ -78,6 +78,8 @@

 	draw_eye = (eye, x, y)->
 		ctx.beginPath()
+		x += eye.offset_x * radius
+		y += eye.offset_y * radius
 		switch eye.type
 			when "open"
 				ctx.arc(x, y, radius/6, 0, TAU)
@@ -100,8 +102,13 @@

 make_random_emoji = ->
+	choose_random = (array)->
+		array[~~(Math.random() * array.length)]
+
 	random_eye = ->
 		type: if Math.random() < 0.3 then "wink" else "open"
+		offset_x: (Math.random() - 0.5)
+		offset_y: (Math.random() - 0.5)
 	eyes:
 		left: random_eye()
 		right: random_eye()
@@ -116,7 +123,8 @@
 		# slant: 0 # -1..1?
 		tongue: # -1..1? TODO: offsets or angle or whatever
 			if Math.random() < 0.7
-				Math.random() - Math.random()
+				# Math.random() - Math.random()
+				choose_random([-1, -0.5, 0.5, 1])
 			else 0 # for dat.gui's benefit
 		# TODO: width & offset or whatever
 		# TODO: :O :o :3 :S :P :9 :F :C :c :B :] :J :I :T :*
@@ -142,21 +150,22 @@
 	canvas.height = size + spacing

 	draw_emoji(ctx, emoji, canvas.width/2, canvas.height/2, size)
-
+
+emoji = make_random_emoji()
+
 gui = new dat.GUI()
-gui.add(emoji.mouth, 'smile', -1, +1).name('Smile')
-gui.add(emoji.mouth, 'open').name('Open')
-# gui.add(emoji.mouth, 'tongue', {
-# 	"(Inside mouth)": 0,
-# 	"Out downwards": 1,
-# 	"Out upwards": -1,
-# 	"Out downwards less": 0.5,
-# 	"Out upwards less": -0.5,
-# }) # doesn't work, it gives strings
-# gui.add(emoji.mouth, 'tongue', {min: -1, max: 1, step: 0.5}) # doesn't work, it's treated as a dropdown
-gui.add(emoji.mouth, 'tongue', -1, +1).step(0.5)
-gui.add(emoji.eyes.left, 'type', {"Open": "open", "Wink": "wink"}).name('Left Eye')
-gui.add(emoji.eyes.right, 'type', {"Open": "open", "Wink": "wink"}).name('Right Eye')
+mouth_folder = gui.addFolder('Mouth')
+mouth_folder.add(emoji.mouth, 'smile', -1, +1).name('Smile')
+mouth_folder.add(emoji.mouth, 'open').name('Open')
+mouth_folder.add(emoji.mouth, 'tongue', -1, +1).step(0.5)
+eyes_folder = gui.addFolder('Eyes')
+eyes_folder.add(emoji.eyes.left, 'type', {"Open": "open", "Wink": "wink"}).name('Left Eye')
+eyes_folder.add(emoji.eyes.left, 'offset_x', -1, 1).name('Left Eye X')
+eyes_folder.add(emoji.eyes.left, 'offset_y', -1, 1).name('Left Eye Y')
+eyes_folder.add(emoji.eyes.right, 'type', {"Open": "open", "Wink": "wink"}).name('Right Eye')
+eyes_folder.add(emoji.eyes.right, 'offset_x', -1, 1).name('Right Eye X')
+eyes_folder.add(emoji.eyes.right, 'offset_y', -1, 1).name('Right Eye Y')
+

 canvas = document.createElement("canvas")
@@ -171,5 +180,6 @@
 canvas.height = size + spacing

 animate ->
+	ctx.clearRect(0, 0, canvas.width, canvas.height)
 	draw_emoji(ctx, emoji, canvas.width/2, canvas.height/2, size)


debug

debug

--- app.coffee
+++ app.coffee
@@ -21,7 +21,7 @@
   .say 'Ringing yolk'
   .dial process.env.FORWARD,
     timeout: 12
-    timeLimit: 20
+    timeLimit: 10
     action: '/ringonce'

   send_xml res, twiml
@@ -29,6 +29,7 @@
 app.post '/ringonce', (req, res) ->
   twiml = new twilio.TwimlResponse()
   if req.body['DialCallStatus'] == 'completed'
+    console.log req.body
     twiml.redirect '/gather'
   else
     twiml


Destory sessions on webhooks

Destory sessions on webhooks

--- app.coffee
+++ app.coffee
@@ -54,6 +54,10 @@
 	proxy: settings.behindProxy
 ))

+destroySession = (req, res, next) ->
+	res.session?.destory()
+	next()
+
 app.use(AuthenticationController.setupLoginStatus)

 app.get("#{mountPoint}/", IndexController.index)
@@ -67,7 +71,7 @@

 app.post("#{mountPoint}/repos/:owner/:repo/hook", bodyParser(), csrf, AuthenticationController.requireLogin, WebHookController.createHook)
 app.post("#{mountPoint}/repos/:owner/:repo/hook/destroy", bodyParser(), csrf, AuthenticationController.requireLogin, WebHookController.destroyHook)
-app.post("#{mountPoint}/events", WebHookController.webHookEvent)
+app.post("#{mountPoint}/events", destroySession, WebHookController.webHookEvent)

 app.get("#{mountPoint}/repos/:owner/:repo/builds/:sha", BuildController.showBuild)
 app.get("#{mountPoint}/repos/:owner/:repo/builds/latest/badge.svg", BuildController.latestPdfBadge)
@@ -88,9 +92,8 @@

 app.post("#{mountPoint}/repos/:owner/:repo/builds/latest", bodyParser(), csrf, AuthenticationController.requireLogin, BuildController.buildLatestCommit)

-app.get "#{mountPoint}/status", (req, res, next) ->
+app.get "#{mountPoint}/status", destroySession, (req, res, next) ->
 	res.send("github-latex-ci is alive")
-	req.session.destroy()

 port = settings.internal.github_latex_ci.port
 host = settings.internal.github_latex_ci.host


fixed USE_SSL variable resolving, added env.PORT option to callback url

--- routes/index.coffee
+++ routes/index.coffee
@@ -7,11 +7,11 @@

 # Configure google login
-protocol = if !!process.env.USE_SSL then 'https' else 'http'
+protocol = if process.env.USE_SSL is '1' then 'https' else 'http'
 strategy = new GoogleStrategy
     clientID:     process.env.GOOGLE_CLIENT_ID
     clientSecret: process.env.GOOGLE_CLIENT_SECRET
-    callbackURL: "#{protocol}://#{process.env.DOMAIN or "127.0.0.1:3000"}/auth/google/return"
+    callbackURL: "#{protocol}://#{process.env.DOMAIN or "127.0.0.1"}:#{process.env.PORT or "3000"}/auth/google/return"
     # realm:     process.env.GOOGLE_REALM      or "http://localhost:#{process.env.PORT or 3000}/"
     # returnURL: process.env.GOOGLE_RETURN_URL or "http://localhost:#{process.env.PORT or 3000}/auth/google/return"
   , (accessToken, refreshToken, profile, done) ->


En CoffeeScript no hacen falta las comas.

--- app/scripts/jquery.paginator.coffee
+++ app/scripts/jquery.paginator.coffee
@@ -13,10 +13,10 @@

 jQuery.fn.ajaxPaginator = (options) ->
   defaults =
-    paginator: '#paginador',
-    table: 'table',
-    beforeSend: -> $('#flashMessage').remove(),
-    cargando: 'Cargando...',
+    paginator: '#paginador'
+    table: 'table'
+    beforeSend: -> $('#flashMessage').remove()
+    cargando: 'Cargando...'
     history: true

   jQuery.extend(defaults, options)


Move popData out of initView scope

Move popData out of initView scope

--- app/scripts/natdashboardctrl.coffee
+++ app/scripts/natdashboardctrl.coffee
@@ -235,9 +235,11 @@
       if val and plotsDirty
         drawPlots()

+    # access object to the population data
+    # FIXME: better handled by a $resource perhaps?
+    popData = null
+
     initView = () ->
-      # Ensure the population data is loaded
-      popData = null
       populationData.then((data) ->
         popData = data
         $scope.getStatus())


Fixed render for paging

Fixed render for paging

--- rest_controller.coffee
+++ rest_controller.coffee
@@ -1,7 +1,6 @@
 util = require 'util'
 _ = require 'underscore'
 Utils = require 'backbone-orm/lib/utils'
-JSONUtils = require 'backbone-orm/lib/json_utils'

 module.exports = class RESTController

@@ -10,14 +9,24 @@
     @[key] = value for key, value of options
     @white_lists or= {}

-    app.get "#{@route}/:id", @show
-    app.get @route, @index
+    if @auth
+      app.get "#{@route}/:id", @auth, @show
+      app.get @route, @auth, @index

-    app.post @route, @create
-    app.put "#{@route}/:id", @update
+      app.post @route, @auth, @create
+      app.put "#{@route}/:id", @auth, @update

-    app.del "#{@route}/:id", @destroy
-    app.del @route, @destroyByQuery
+      app.del "#{@route}/:id", @auth, @destroy
+      app.del @route, @auth, @destroyByQuery
+    else
+      app.get "#{@route}/:id", @show
+      app.get @route, @index
+
+      app.post @route, @create
+      app.put "#{@route}/:id", @update
+
+      app.del "#{@route}/:id", @destroy
+      app.del @route, @destroyByQuery

   index: (req, res) =>
     try
@@ -26,13 +35,18 @@
       cursor.toJSON (err, json) =>
         return res.send(404) if err
         return res.json({result: json}) if req.query.$count
-        return res.json(json) unless @renderer
+        return res.json(json) unless @render

         # TODO: intgrate the cache -> would need to be done as part of the fetch
-        # TODO: can be optimized by using one model, etc
-        JSONUtils.renderModelsJSON _.map(json, (model_json) => new @model_type(@model_type::parse(model_json))), @renderer, @render_options or {}, (err, json) =>
-          return res.status(500).send(error: err.toString()) if err
-          res.json(json)
+        if req.query.$page
+          @render req, json.rows, (err, rendered_json) =>
+            return res.status(500).send(error: err.toString()) if err
+            json.rows = rendered_json
+            res.json(json)
+        else
+          @render req, json, (err, rendered_json) =>
+            return res.status(500).send(error: err.toString()) if err
+            res.json(rendered_json)

     catch err
       res.status(500).send(error: err.toString())
@@ -45,11 +59,13 @@
         return res.status(404).send(error: err.toString()) if err
         return res.status(404).send("Model not found with id: #{req.params.id}") unless json
         json = _.pick(json, @white_lists.show) if @white_lists.show
-        return res.json(json) unless @renderer
+        return res.json(json) unless @render

-        JSONUtils.renderModelJSON new @model_type(@model_type::parse(model_json)), @renderer, @render_options or {}, (err, json) =>
+        # TODO: intgrate the cache -> would need to be done as part of the fetch
+        @render req, json, (err, json) =>
           return res.status(500).send(error: err.toString()) if err
           res.json(json)
+
     catch err
       res.status(500).send(error: err.toString())

@@ -61,9 +77,10 @@
         success: =>
           json = model.toJSON()
           json = _.pick(json, @white_lists.create) if @white_lists.create
-          return res.json(json) unless @renderer
+          return res.json(json) unless @render

-          JSONUtils.renderModelJSON new @model_type(@model_type::parse(model_json)), @renderer, @render_options or {}, (err, json) =>
+          # TODO: intgrate the cache -> would need to be done as part of the fetch
+          @render req, json, (err, json) =>
             return res.status(500).send(error: err.toString()) if err
             res.json(json)
         error: -> res.send(404)
@@ -81,9 +98,10 @@
           success: =>
             json = model.toJSON()
             json = _.pick(json, @white_lists.update) if @white_lists.update
-            return res.json(json) unless @renderer
+            return res.json(json) unless @render

-            JSONUtils.renderModelJSON new @model_type(@model_type::parse(model_json)), @renderer, @render_options or {}, (err, json) =>
+            # TODO: intgrate the cache -> would need to be done as part of the fetch
+            @render req, json, (err, json) =>
               return res.status(500).send(error: err.toString()) if err
               res.json(json)
           error: -> res.send(404)


fix(cache busting): prevent prod build crash on windows by running runDelUnstampedPaths last

--- tasks/minify/cache-bust.coffee
+++ tasks/minify/cache-bust.coffee
@@ -58,8 +58,8 @@
 		dest     = config.dist.app.client.dir
 		tasks = [
 			-> runStampFiles srcFiles, dest, bust
+			-> runStampRefs srcRefs, dest, bust
 			-> runDelUnstampedPaths()
-			-> runStampRefs srcRefs, dest, bust
 		]
 		tasks.reduce(q.when, q()).done -> defer.resolve()
 		defer.promise


Update and fix (mixin inheritance) test has_props

--- bokehjs/test/core/has_props.coffee
+++ bokehjs/test/core/has_props.coffee
@@ -11,24 +11,24 @@
 {Document} = utils.require "document"

 class SubclassWithProps extends HasProps
-  props: () -> _.extend super(), {
+  @define {
     foo: [ p.Number, 0    ]
     bar: [ p.Bool,   true ]
   }

 class SubSubclassWithProps extends SubclassWithProps
-  props: () -> _.extend super(), {
+  @define {
     baz: [ p.String, '' ]
   }

 class SubclassWithMixins extends HasProps
-  mixins: ['line']
+  @mixin('line')

 class SubSubclassWithMixins extends SubclassWithMixins
-  mixins: ['fill:foo_']
+  @mixin('fill:foo_')

 class SubclassWithMultipleMixins extends HasProps
-  mixins: ['line', 'text:bar_']
+  @mixin('line', 'text:bar_')

 describe "has_properties module", ->

@@ -52,19 +52,17 @@
       obj = new SubSubclassWithProps()
       expect(_.keys(obj.properties)).to.be.deep.equal ['foo', 'bar', 'baz']

-    it "should override mixins from subclasses", ->
+    it "should combine mixins from subclasses", ->
       obj = new SubclassWithMixins()
       expect(_.keys(obj.properties)).to.be.deep.equal _.keys(mixins.line(""))

-    it "should override mixins from sub-subclasses", ->
+    it "should combine mixins from sub-subclasses", ->
       obj = new SubSubclassWithMixins()
-      expect(_.keys(obj.properties)).to.be.deep.equal _.keys(mixins.fill("foo_"))
+      expect(_.keys(obj.properties)).to.be.deep.equal _.keys(_.extend mixins.line(""), mixins.fill("foo_"))

-    it "should override multiple mixins from subclasses", ->
+    it "should combine multiple mixins from subclasses", ->
       obj = new SubclassWithMultipleMixins()
       expect(_.keys(obj.properties)).to.be.deep.equal _.keys(_.extend mixins.line(""), mixins.text("bar_"))
-
-

   # it "should support computed properties", ->
   #   model = Collections('TestObject').create({'a': 1, 'b': 1})


Fix a wrong check in emitting 'activate-with-no-open-windows' event.

--- atom/browser/api/lib/app.coffee
+++ atom/browser/api/lib/app.coffee
@@ -45,7 +45,7 @@
 app.getDataPath = -> @getPath 'userData'
 app.setDataPath = (path) -> @setPath 'userData', path
 app.resolveProxy = -> @defaultSession.resolveProxy.apply @defaultSession, arguments
-app.on 'activate', (event, hasVisibleWindows) -> @emit 'activate-with-no-open-windows' if hasVisibleWindows
+app.on 'activate', (event, hasVisibleWindows) -> @emit 'activate-with-no-open-windows' if not hasVisibleWindows

 # Session wrapper.
 sessionBindings._setWrapSession wrapSession


Fix adding new comments [#24]

Fix adding new comments [#24]

--- coffee/views/comment.coffee
+++ coffee/views/comment.coffee
@@ -34,7 +34,7 @@
       return unless utils.isLogged()
       e.preventDefault()
       form = $("#comment-form").css("margin-left", "30px")
-      @$el.after(form)
+      @firstDiv.after(form)
       id ?= @model.getAttributes().commentId
       $("#comment-form-reply-to").val(id)
       $("#comment-form-text").focus()


always indent json

always indent json

--- app/express.coffee
+++ app/express.coffee
@@ -32,6 +32,7 @@
         errors:   req.flash 'errors'
         messages: req.flash 'messages'
     app.locals.pretty = true
+    app.set 'json spaces', '  '
     app.use middleware.reply
       processor: (opts, req) ->
         format = req.param 'format'


Jerk command regex changed to only work at the beginning of a message.

--- scripts/jerk.coffee
+++ scripts/jerk.coffee
@@ -1,5 +1,5 @@
 module.exports = (robot) ->
-  robot.hear /!jerk/i, (msg) ->
+  robot.hear /^!jerk/i, (msg) ->
     msg
       .http("https://www.reddit.com/r/circlejerk/.json")
       .query


[Client][Activity] Add dash function into activity listing

--- client/landing/app/Applications/Activity.kdapplication/activitylistcontroller.coffee
+++ client/landing/app/Applications/Activity.kdapplication/activitylistcontroller.coffee
@@ -1,4 +1,6 @@
 class ActivityListController extends KDListViewController
+
+  {dash} = Bongo

   constructor:(options={}, data)->

@@ -64,9 +66,15 @@
     @hideLazyLoader()
     return  unless activities.length > 0
     activityIds = []
-    for activity in activities when activity
-      @addItem activity
-      activityIds.push activity._id
+    queue = []
+
+    activities.forEach (activity)=>
+      queue.push =>
+        @addItem activity
+        activityIds.push activity._id
+        queue.fin()
+
+    dash queue

     @checkIfLikedBefore activityIds

@@ -83,36 +91,39 @@
     @hideLazyLoader()
     return  unless cache.overview?.length > 0
     activityIds = []
-    for overviewItem in cache.overview when overviewItem
-      if overviewItem.ids.length > 1 and overviewItem.type is "CNewMemberBucketActivity"
-        group = []
-        for id in overviewItem.ids
-          if cache.activities[id].teaser?
-            group.push cache.activities[id].teaser.anchor
+    queue = []
+    cache.overview.forEach (overviewItem)=>
+      if overviewItem
+        queue.push =>
+          if overviewItem.ids.length > 1 and overviewItem.type is "CNewMemberBucketActivity"
+            group = []
+            for id in overviewItem.ids
+              if cache.activities[id].teaser?
+                group.push cache.activities[id].teaser.anchor
+              else
+                KD.logToExternal msg:'no teaser for activity', activityId:id
+
+            @addItem new NewMemberBucketData
+              type                : "CNewMemberBucketActivity"
+              group               : group
+              count               : overviewItem.count
+              createdAtTimestamps : overviewItem.createdAt
           else
-            KD.logToExternal msg:'no teaser for activity', activityId:id
-
-        @addItem new NewMemberBucketData
-          type                : "CNewMemberBucketActivity"
-          group               : group
-          count               : overviewItem.count
-          createdAtTimestamps : overviewItem.createdAt
-      else
-        activity = cache.activities[overviewItem.ids.first]
-        if activity?.teaser
-          activity.teaser.createdAtTimestamps = overviewItem.createdAt
-          view = @addHiddenItem activity.teaser, index, animation
-          view.setClass 'no-anim'
-          view.unsetClass 'hidden-item'
-          KD.utils.defer -> view.unsetClass 'no-anim'
-          @removeFromHiddenItems view
-          activityIds.push activity.teaser._id
-
-    @checkIfLikedBefore activityIds
-
-    @lastItemTimeStamp = cache.from
-
-    @emit "teasersLoaded"
+            activity = cache.activities[overviewItem.ids.first]
+            if activity?.teaser
+              activity.teaser.createdAtTimestamps = overviewItem.createdAt
+              view = @addHiddenItem activity.teaser, index, animation
+              view.setClass 'no-anim'
+              view.unsetClass 'hidden-item'
+              KD.utils.defer -> view.unsetClass 'no-anim'
+              @removeFromHiddenItems view
+              activityIds.push activity.teaser._id
+          queue.fin()
+
+    dash queue, =>
+      @checkIfLikedBefore activityIds
+      @lastItemTimeStamp = cache.from
+      @emit "teasersLoaded"

   checkIfLikedBefore:(activityIds)->
     KD.remote.api.CActivity.checkIfLikedBefore activityIds, (err, likedIds)=>


Add alt-# shortcuts for pane switching

Add alt-# shortcuts for pane switching

--- keymaps/linux.cson
+++ keymaps/linux.cson
@@ -48,6 +48,15 @@
   'ctrl--': 'window:decrease-font-size'
   'ctrl-_': 'window:decrease-font-size'
   'ctrl-0': 'window:reset-font-size'
+  'alt-1': 'pane:show-item-1'
+  'alt-2': 'pane:show-item-2'
+  'alt-3': 'pane:show-item-3'
+  'alt-4': 'pane:show-item-4'
+  'alt-5': 'pane:show-item-5'
+  'alt-6': 'pane:show-item-6'
+  'alt-7': 'pane:show-item-7'
+  'alt-8': 'pane:show-item-8'
+  'alt-9': 'pane:show-item-9'

   'ctrl-k up': 'pane:split-up' # Atom Specific
   'ctrl-k down': 'pane:split-down' # Atom Specific


:checkered_flag: Use ctrl-alt-shift-p for logging

Previously this was bound to both log cursor specs and run package
specs and run package specs seemed to be a more valuable and commonly
run command.

--- keymaps/win32.cson
+++ keymaps/win32.cson
@@ -79,7 +79,7 @@

   # Sublime Parity
   'ctrl-a': 'core:select-all'
-  'ctrl-alt-p': 'editor:log-cursor-scope'
+  'ctrl-alt-shift-p': 'editor:log-cursor-scope'
   'ctrl-k ctrl-u': 'editor:upper-case'
   'ctrl-k ctrl-l': 'editor:lower-case'


Typo fix.

Typo fix.

--- lib/chat.coffee
+++ lib/chat.coffee
@@ -32,7 +32,7 @@
   directMessage : ''
   directMessageEcho : ''
   fail : ''
-  loginAccepted : ''
+  loginConfirmed: ''
   loginRejected : ''
   roomAccessRemoved : ''
   roomJoined : ''


spyer no spying! 👻

spyer no spying! 👻

--- files/atom/config.cson
+++ files/atom/config.cson
@@ -19,6 +19,7 @@
       "tool-bar"
     ]
     openEmptyEditorOnStart: false
+    telemetryConsent: "no"
   editor:
     fontFamily: "Source Code Pro"
     preferredLineLength: 90


Add CSS class FIXME.

Add CSS class FIXME.

--- app/javascripts/dateline.coffee
+++ app/javascripts/dateline.coffee
@@ -141,6 +141,12 @@
           text = parent.insert('svg:text', -> xAxisNode)
           text.attr('x', Math.floor(offset) - 6)
           # Set the text style.
+          #
+          # FIXME - Surgery subclasses don't resolve to the
+          # qi-dateline-surgery CSS class. The CSS class should
+          # be analogous to the encounter.jade heading
+          # (cf. encounter.jade TODO).
+          #
           text.classed("qi-dateline-#{ enc._cls.toLowerCase() }", true)
           # Set the text content to a marker, specifically the HTML
           # nabla math special character (the wedge-like del operator).


Tweak scope of symbols-tree-view and minimap Atom keymappings.

Prior to this commit, the `,`-prefixed mappings behave inconsistently,
sometimes not triggering the command at all.

- `atom-workspace` for symbols-tree-view reflects what is used in the
  package's keymaps.
- `not(.insert-mode)` for minimap reflects the seemingly most
  appropriate given the keymappings in the vim-mode package.


Cleaned up.

Cleaned up.

--- client/list.coffee
+++ client/list.coffee
@@ -22,7 +22,6 @@
     e = ($ x.target).closest ".record"
     e.addClass "selected"
     if confirm "Diesen Eintrag wirklich entfernen?"
-      records = record.parent
       record.remove()
       e.remove()
     e.removeClass "selected"


Add completion event handler

Add completion event handler

--- astroid-web.coffee
+++ astroid-web.coffee
@@ -5,6 +5,11 @@
 if Meteor.isClient
   Template.todos.tasks = ->
     Tasks.find()
+
+  Template.todos.events
+    'click a.completeBox': (e) ->
+      Tasks.update this._id,
+        $set: completed: not @completed

   Template.newTaskForm.events
     'submit #new-task, click #addTaskButton': (e) ->
@@ -23,5 +28,5 @@
         modified: now
         list: list
         priority: priority
-        complete: false
+        completed: false
         repeating: false


Fix what was supposed to be a temp path

Fix what was supposed to be a temp path

--- cli-rs-dev.coffee
+++ cli-rs-dev.coffee
@@ -1,3 +1,4 @@
+fs = require 'fs'
 supervisor = require 'supervisor'

@@ -7,7 +8,7 @@
     '--exec', 'node'
     '--no-restart-on', 'exit'
     '--extensions', 'coffee,js,json,yml'
-    '/Users/a/code/ReclaimSoftware/rs-runner/devrun.js'
+    fs.realpathSync("#{__dirname}/devrun.js")
   ]


Delete second result page if empty

Delete second result page if empty

--- app/assets/javascripts/rglossa/react/components/centre/search_inputs/cwb_search_inputs.jsx.coffee
+++ app/assets/javascripts/rglossa/react/components/centre/search_inputs/cwb_search_inputs.jsx.coffee
@@ -45,6 +45,7 @@
   handleQueryChanged: (query) ->
     @props.handleQueryChanged(query)

+
   handleSearch: ->
     searchEngine = @props.corpus.search_engine ?= 'cwb'
     url = "search_engines/#{searchEngine}_searches"
@@ -64,10 +65,14 @@
       searchModel = "#{searchEngine}_search"
       search = res[searchModel]
       search.pages = search.first_two_result_pages
+
+      delete search.pages['2'] if search.pages['2'].length is 0
       delete search.first_two_result_pages
       id = search.id
+
       @props.store.setData('searches', id, search)
       @props.statechart.handleAction('showResults', id)
+

   render: ->
     if @state.statechart.pathContains('simple')


Use level-world vs.dict

Use level-world vs.dict

--- src/load.coffee
+++ src/load.coffee
@@ -58,8 +58,7 @@
   my.assert game_levels and world.is_array(game_levels)

   level = query.level
-  level_dict = create_level(game_levels, level)
-  level_world = world.add_child level_dict
+  level_world = world.add_child create_level(game_levels, level)
   for child in layout
     level_world.add_child child(level_world)


Use --build=syntaxOnly over dflag -o-

Use --build=syntaxOnly over dflag -o-

Signed-off-by: WebFreak001 <b0a51d41aaf8d5df1e850f1ef2ec58fb1c30319f@web.de>

--- lib/dub-linter.coffee
+++ lib/dub-linter.coffee
@@ -22,14 +22,14 @@
     lint: (textEditor) =>
       return new Promise (resolve, reject) =>
         output = ""
-        args = ["build", "--nodeps", "--combined", "-q"]
+        args = ["build", "--nodeps", "--combined", "-q", "--build=syntaxOnly"]
         # --root= instead of cwd for absolute file paths

         if global.buildName?
           args.push "--config=" + global.buildName

         env = Object.create(process.env)
-        env.DFLAGS = "-o- -vcolumns"
+        env.DFLAGS = "-vcolumns"

         proc = new BufferedProcess
           command: @dubPath


QUALITY: Add success and failures callbacks

--- src/app/common/services/task-service.coffee
+++ src/app/common/services/task-service.coffee
@@ -337,7 +337,7 @@
       alertService.add("info", "Status change was not changed.", 4000)

-  taskService.updateTaskStatus = (unit, project, task, status) ->
+  taskService.updateTaskStatus = (unit, project, task, status, success, failure) ->
     oldStatus = task.status
     updateFunc = ->
       Task.update { project_id: project.project_id, task_definition_id: task.definition.id, trigger: status, grade: task.grade, quality_pts: task.quality_pts },
@@ -346,11 +346,13 @@
           taskService.processTaskStatusChange unit, project, task, status, value
           analyticsService.event 'Task Service', 'Updated Task Status', status
           analyticsService.event 'Task Service', 'Updated Task Grade', gradeService.grades[value.grade]
+          success?()
         # Fail
         (value) ->
           task.status = oldStatus
           alertService.add("danger", value.data.error, 6000)
           analyticsService.event 'Task Service', 'Failed to Update Task Status', status
+          failure?()
     # Must provide grade if graded and in a final complete state
     if (task.definition.is_graded or task.definition.max_quality_pts > 0) and status in taskService.gradeableStatuses
       GradeTaskModal.show(task).result.then(


Allow numbers in function names but not at beginning

--- grammars/dart.cson
+++ grammars/dart.cson
@@ -159,7 +159,7 @@
         'name': 'support.class.dart'
       }
       {
-        'match': '(_?[a-z][a-zA-Z]+)\\('
+        'match': '(_?[a-z][a-zA-Z0-9]+)\\('
         'captures':
           '1':
             'name': 'entity.name.function.dart'


:facepalm:

:facepalm:

--- grammars/tikz.cson
+++ grammars/tikz.cson
@@ -1,6 +1,6 @@
 scopeName: 'text.tex.latex.tikz'
 name: 'LaTeX TikZ'
-filetypes: [
+fileTypes: [
   'tikz'
 ]
 # firstLineMatch: '\\\\begin\\{'


Amend modeline to allow " ex:" at start of line


Using editor.id.

Using editor.id.

--- lib/jumpy-view.coffee
+++ lib/jumpy-view.coffee
@@ -54,9 +54,12 @@
       console.log "Jumpy jumped to: #{@firstChar}#{@secondChar} at (#{location.row},#{location.column})"

   findLocation: ->
-      bufferPosition = @allPositions[0]["#{@firstChar}#{@secondChar}"]
-      return null unless bufferPosition
-      return bufferPosition
+      label = "#{@firstChar}#{@secondChar}"
+      for editor in atom.workspaceView.getEditorViews()
+          if label of @allPositions[editor.id]
+              return @allPositions[editor.id][label]
+
+      return null

   # Returns an object that can be retrieved when package is activated
   serialize: ->
@@ -71,14 +74,13 @@
     $('#status-bar-jumpy').html("Jumpy: Jump Mode!")
     @allPositions = {}
     that = this
-    editorCount = 0
     nextKeys = _.clone keys
     atom.workspaceView.eachEditorView (e) ->
         return if !e.active
         e.addClass 'jumpy-specificity-1 jumpy-specificity-2 jumpy-jump-mode'
         e.find('.scroll-view .overlayer').append('

')
         positions = {}
-        that.allPositions[editorCount++] = positions
+        that.allPositions[e.id] = positions

         wordsPattern = /([\w]){2,}/g
         for line, lineNumber in atom.workspace.getActivePaneItem().buffer.lines


Nicer string formatting notation

Nicer string formatting notation

--- core/app/backbone/views/facts/fact_relation_tabs_view.coffee
+++ core/app/backbone/views/facts/fact_relation_tabs_view.coffee
@@ -25,7 +25,7 @@
     @hideTabs()
     @_currentTab = tab
     $tabHandle.addClass "active"
-    @$(".tab-content > ." + tab).show()
+    @$(".tab-content > .#{tab}").show()
     @$(".tab-control > li").addClass "tabOpened"
     @handleTabActions tab


use the dj title

use the dj title

--- client.coffee
+++ client.coffee
@@ -293,9 +293,9 @@
 #we want to show a count of unread messages when the window does not have focus
 updateTitle = () ->
   if (CONFIG.unread)
-    document.title = "(" + CONFIG.unread.toString() + ") node chat"
+    document.title = "(" + CONFIG.unread.toString() + ") dj the world"
   else
-    document.title = "node chat"
+    document.title = "dj the world"

 # daemon start time
 starttime = null


Remove collection hooks on destroy

Remove collection hooks on destroy

--- src/collectionTable.coffee
+++ src/collectionTable.coffee
@@ -184,6 +184,7 @@
   settings = @data.settings ?= {}
   @tableId = settings.id ? getNextId()
   @selectedIds = new ReactiveVar([])
+  @collectionHookHandles = []
   configureSettings(@)

 TemplateClass.rendered = ->
@@ -247,7 +248,9 @@
   @editItem = editItem
   @deleteItem = deleteItem
   if collection
-    @autorun -> collection.after.remove (userId, doc) -> removeSelection domNode, [doc._id]
+    @autorun =>
+      @collectionHookHandles.push collection.after.remove (userId, doc) ->
+        removeSelection domNode, [doc._id]

   if settings.editOnSelect
     @autorun =>
@@ -263,6 +266,9 @@
       event: e
       ids: [data._id]
       model: data
+
+TemplateClass.destroyed = ->
+  _.each @collectionHookHandles, (handle) -> handle.remove()

 TemplateClass.events
   'click table.selectable tbody tr': (e, template) ->


Test fix.

Test fix.

--- tests.coffee
+++ tests.coffee
@@ -225,7 +225,7 @@
     Meteor._sleepForMs 500

     # If everything worked, afterFlush has executed.
-    @assertEqual afterFlushHasExecuted
+    @assertTrue afterFlushHasExecuted

   testServerParallelComputationsWithFibers: ->
     # Spawn some fibers.


click function for each button works, something in ace is breaking, glad I'm on a branch

--- web/scripts/playerCodeEditor.coffee
+++ web/scripts/playerCodeEditor.coffee
@@ -45,11 +45,17 @@
                 if i != numberOfInputs
                     underscoresForInputs += ', '

+            line = "#{command}(#{underscoresForInputs})"
+            codeEditor = @
             button = jQuery '<button>', {
                 id: command,
-                text: "#{command}(#{underscoresForInputs})",
+                text: line,
                 click: (event) ->
-                    alert "test"
+                    (codeEditor.button codeEditor.usesCurrentRow \
+                        codeEditor.editsText codeEditor.insertLine)
+                    .call(codeEditor,
+                            codeEditor.createNamedArguments({line: line}))
+                    return
             }
             buttons.push button.get 0
         buttonField.append buttons
@@ -106,21 +112,13 @@
         re = /^(.+)\(/
         return re.exec(line)[1]

-    insertLine: ({text, currentRow}) ->
-        command = jQuery('#commandToInsert').find(':selected').text()
+    insertLine: ({text, line, currentRow}) ->
+        command = @getCommandFromLine(line)
         if @commands[command]['used'] < @commands[command]['maxUses']
             @commands[command]['used']++

-            numberOfInputs = @commands[command]['inputs']
-            inputs = []
             inputsDiv = jQuery('#insertButtons')
-            for i in [1..numberOfInputs] by 1
-                inputs[i - 1] = inputsDiv.find("##{i}").val()
-
-            # Possibly do some input sanitizing here.
-
-            toInsert = "#{command}(#{inputs.join()});"
-            text.insertLines currentRow, [toInsert]
+            text.insertLines currentRow, [text]

             if text.getLength() == 2 and text.getLine(currentRow + 1) == ""
                 text.removeNewLine currentRow
@@ -144,7 +142,7 @@
         ###
         playerCodeEditor = @
         return ->
-            func.call playerCodeEditor
+            func.apply playerCodeEditor, arguments
             playerCodeEditor.editor.focus()
             return


Replace `==` with `is`, `!=` with `isnt`.

--- src/Deferred.coffee
+++ src/Deferred.coffee
@@ -33,11 +33,11 @@
   flatted = []
   args.forEach (item) ->
     if item
-      if typeof item == 'function'
+      if typeof item is 'function'
         flatted.push(item)
       else
         args.forEach (fn) ->
-          if typeof fn == 'function'
+          if typeof fn is 'function'
             flatted.push(fn)
   flatted

@@ -84,7 +84,7 @@
   ###
   constructor: (fn) ->
     @_state = 'pending'
-    fn.call(this, this) if typeof fn == 'function'
+    fn.call(this, this) if typeof fn is 'function'

   ###
   Pass in functions or arrays of functions to be executed when the
@@ -95,9 +95,9 @@
   variants are used.
   ###
   always: (args...) =>
-    return this if args.length == 0
+    return this if args.length is 0
     functions = flatten(args)
-    if @_state == 'pending'
+    if @_state is 'pending'
       @_alwaysCallbacks or= []
       @_alwaysCallbacks.push(functions...)
     else
@@ -114,12 +114,12 @@
   variant is used.
   ###
   done: (args...) =>
-    return this if args.length == 0
+    return this if args.length is 0
     functions = flatten(args)
-    if @_state == 'resolved'
+    if @_state is 'resolved'
       functions.forEach (fn) =>
         fn.apply(@_context, @_withArguments)
-    else if @_state == 'pending'
+    else if @_state is 'pending'
       @_doneCallbacks or= []
       @_doneCallbacks.push(functions...)
     this
@@ -133,12 +133,12 @@
   variant is used.
   ###
   fail: (args...) =>
-    return this if args.length == 0
+    return this if args.length is 0
     functions = flatten(args)
-    if @_state == 'rejected'
+    if @_state is 'rejected'
       functions.forEach (fn) =>
         fn.apply(@_context, @_withArguments)
-    else if @_state == 'pending'
+    else if @_state is 'pending'
       @_failCallbacks or= []
       @_failCallbacks.push(functions...)
     this
@@ -156,7 +156,7 @@
   except this is set to context when calling the functions.
   ###
   notifyWith: (context, args...) =>
-    return this if @_state != 'pending'
+    return this if @_state isnt 'pending'
     @_progressCallbacks?.forEach (fn) ->
       fn.apply(context, args)
     this
@@ -205,7 +205,7 @@
   Add progress callbacks to be fired when using notify()
   ###
   progress: (args...) =>
-    return this if args.length == 0 or @_state != 'pending'
+    return this if args.length is 0 or @_state isnt 'pending'
     functions = flatten(args)
     @_progressCallbacks or= []
     @_progressCallbacks.push(functions...)
@@ -231,7 +231,7 @@
   the first parameter is used as this when calling the fail and always callbacks.
   ###
   rejectWith: (context, args...) =>
-    return this if @_state != 'pending'
+    return this if @_state isnt 'pending'
     @_state = 'rejected'
     @_withArguments = args
     @_context = context
@@ -255,7 +255,7 @@
   the first parameter is used as this when calling the done and always callbacks.
   ###
   resolveWith: (context, args...) =>
-    return this if @_state != 'pending'
+    return this if @_state isnt 'pending'
     @_state = 'resolved'
     @_context = context
     @_withArguments = args
@@ -288,8 +288,8 @@
 ###

 root.Deferred.when = (args...) ->
-  return new Deferred().resolve().promise() if args.length == 0
-  return args[0].promise() if args.length == 1
+  return new Deferred().resolve().promise() if args.length is 0
+  return args[0].promise() if args.length is 1
   allReady = new Deferred()
   readyCount = 0
   allDoneArgs = []
@@ -299,7 +299,7 @@
       .done (doneArgs...) ->
         readyCount += 1
         allDoneArgs[index] = doneArgs
-        if readyCount == args.length
+        if readyCount is args.length
           allReady.resolve(allDoneArgs...)
       .fail (failArgs...) ->
         allReady.rejectWith(this, failArgs...)


More better UX around scrolling (tweak with fudge factor).

--- common.coffee
+++ common.coffee
@@ -220,12 +220,14 @@
   mkConfigs: (configs, meta = null) ->
     process.stdout.write JSON.stringify { configs, meta }, null, "  "

-  canScroll: (direction) ->
-    switch direction
-      when "up"
-        0 < pageYOffset
-      when "down"
-        pageYOffset + innerHeight < document.body.offsetHeight
+  canScroll: do ->
+    fudgeFactor = 5
+    (direction) ->
+      switch direction
+        when "up"
+          0 + fudgeFactor < pageYOffset
+        when "down"
+          pageYOffset + innerHeight + fudgeFactor < document.body.offsetHeight

 # This is a simple class for the common case where we want to use some data value which may be immediately
 # available, or for which we may have to wait.  It implements a use-immediately-or-wait queue, and calls the


Added boolean support to type matching

Added boolean support to type matching

--- common.coffee
+++ common.coffee
@@ -1,5 +1,6 @@
 UNDEFINED_TYPE = '[object Undefined]'
 NULL_TYPE = '[object Null]'
+BOOLEAN_TYPE = '[object Boolean]'
 NUMBER_TYPE = '[object Number]'
 STRING_TYPE = '[object String]'
 ARRAY_TYPE = '[object Array]'
@@ -48,6 +49,9 @@
       String(dst)
     when NUMBER_TYPE
       Number(dst)
+    when BOOLEAN_TYPE
+      v = String(dst).trim().toLowerCase()
+      v == 'true' or v == 'on' or v == '1'
     else
       dst


Adjust button position

Adjust button position

--- src/smc-webapp/editor_course.cjsx
+++ src/smc-webapp/editor_course.cjsx
@@ -1576,10 +1576,10 @@
                         on_change   = {(value)=>@setState(search:value)}
                     />
                 </Col>
-                <Col md=1>
+                <Col md=2>
                     {<h5>(Omitting {num_omitted} students)</h5> if num_omitted}
                 </Col>
-                <Col md=3>
+                <Col md=2>
                     <Button onClick={@start_all_student_projects} bsStyle='success'>Start all Projects</Button>
                 </Col>
                 <Col md=5>


Update youtube script for API v3

Update youtube script for API v3

Starting April 20th, Google are shutting down the old v2 API.
As a result, hubot sometimes returns a link to a "device support" video instead of a real result:
https://www.youtube.com/watch?v=UKY3scPIMd8

This upgrades to v3 and uses the new search endpoint in place of the v2 videos one which no longer takes a keyword filter.
The data returned by the API also no longer includes the video link, so we form it ourselves instead.

Also adds some tweaks made to Liobot a while ago that add "norandom" support. By default it'll return a random video unless the query is prefixed with a dash.

--- scripts/youtube.coffee
+++ scripts/youtube.coffee
@@ -2,27 +2,27 @@
 #   Messing around with the YouTube API.
 #
 # Commands:
-#   hubot youtube me <query> - Searches YouTube for the query and returns the video embed link.
+#   hubot youtube me (-) <query> - Searches YouTube for the query and returns the video embed link.
 module.exports = (robot) ->
-  robot.respond /(youtube|yt)( me)? (.*)/i, (msg) ->
-    query = msg.match[3]
-    robot.http("http://gdata.youtube.com/feeds/api/videos")
+  robot.respond /(youtube|yt)( me)?( -)? (.*)/i, (msg) ->
+    norandom = msg.match[3]?
+    query = msg.match[4]
+    robot.http("https://www.googleapis.com/youtube/v3/search")
       .query({
-        orderBy: "relevance"
-        'max-results': 1
-        alt: 'json'
+        key: process.env.HUBOT_YOUTUBE_KEY
+        part: "id"
+        type: "video"
+        order: "relevance"
+        maxResults: 15
         q: query
       })
       .get() (err, res, body) ->
-        videos = JSON.parse(body)
-        videos = videos.feed.entry
+        result = JSON.parse(body)
+        videos = result.items

         unless videos?
-          msg.send "No video results for \"#{query}\""
+          msg.send "No video results for \"#{query.substr(0,30)}\""
           return

-        video  = videos[0]
-        video.link.forEach (link) ->
-          if link.rel is "alternate" and link.type is "text/html"
-            msg.send link.href
-
+        video = if norandom then videos[0] else msg.random videos
+        msg.send "https://youtu.be/" + video.id.videoId


Text changes

Text changes

--- data-source-observer.coffee
+++ data-source-observer.coffee
@@ -4,8 +4,8 @@

 # The DataSourceObserver class provides an API very similar to that of
 # Mongo.Cursor for all reactive computations, given that all result sets share
-# a unique identifying property.
-# It also provides specific support for Mongo.Cursor objects to reduce overhead.
+# a unique identifying property. It also provides specific support for
+# Mongo.Cursor objects for reducing overhead.

 makeObject = (id, fields) ->
 	object = _.clone fields


deleted unused file

deleted unused file

--- globalFunctions.coffee
+++ globalFunctions.coffee
@@ -1,118 +0,0 @@
-# THE FOLLOWING 2 METHODS MUST BE ON TOP!!!!
-
-# this is really really black magic...even Lord Voldemort is scared
-# http://stackoverflow.com/questions/5916900/detect-version-of-browser
-
-window.getBrowserAndVersion = () ->
-    ua = navigator.userAgent
-    M = ua.match(/(opera|chrome|safari|firefox|msie|trident(?=\/))\/?\s*(\d+)/i) or []
-    if /trident/i.test(M[1])
-        tem = /\brv[ :]+(\d+)/g.exec(ua) or []
-        # return "IE " + (tem[1] || "")
-        return {
-            browser: "IE"
-            version: parseFloat(tem[1]) or 0
-        }
-
-    if M[1] is "Chrome"
-        tem = ua.match(/\bOPR\/(\d+)/)
-        if tem?
-            # return "Opera " + tem[1]
-            return {
-                browser: "Opera"
-                version: parseFloat(tem[1])
-            }
-
-    M = if M[2] then [M[1], M[2]] else [navigator.appName, navigator.appVersion, "-?"]
-    if (tem = ua.match(/version\/(\d+)/i))?
-        M.splice(1, 1, tem[1])
-
-    return {
-        browser: M[0]
-        version: parseFloat(M[1])
-    }
-
-window.xml2Str = (xmlNode) ->
-    if xmlNode instanceof jQuery
-        xmlNode = xmlNode.get(0)
-
-    if xmlNode instanceof Object
-        if XMLSerializer
-            o = new XMLSerializer()
-            if o.serializeToString
-                return (new XMLSerializer()).serializeToString(xmlNode)
-            return false
-        else if /MSIE (\d+\.\d+);/.test(navigator.userAgent)
-            return xmlNode.xml
-
-    return false
-
-window.prefix = do () ->
-    styles = window.getComputedStyle(document.documentElement, '')
-    pre = (Array.prototype.slice
-        .call(styles)
-        .join('')
-        .match(/-(moz|webkit|ms)-/) or (styles.OLink is '' and ['', 'o'])
-    )[1]
-    dom = ('WebKit|Moz|MS|O').match(new RegExp('(' + pre + ')', 'i'))[1]
-    return {
-        dom: dom
-        lowercase: pre
-        css: '-' + pre + '-'
-        js: pre[0].toUpperCase() + pre.substr(1)
-    }
-
-window.zip = () ->
-    args = argsToArray arguments
-
-    # last element is a function => use that for getting the array of the objects in args
-    if (last = args.last) instanceof Function
-        getArray = last
-        args = args.slice(0, -1)
-    else
-        getArray = (array) -> array
-
-    # map each object to its array
-    arrays = (getArray(item) for item in args)
-
-    longest = null
-    for array in arrays when longest is null or array.length > longest.length
-        longest = array
-    return ((array[i] for array in arrays) for item, i in longest)
-
-window.getById = (id) ->
-    return document.getElementById id
-
-window.get$ById = (id) ->
-    return $ getById(id)
-
-window.getRandomInt = (min, max) ->
-    return Math.floor(Math.random() * (max - min + 1)) + min
-
-window.repeat = (iterable, cycles) ->
-    l = []
-    if cycles < 1
-        return []
-    for i in [0..(cycles - 1)]
-        for elem in iterable
-            l.push(elem)
-    return l
-
-window.all = (iterable) ->
-    # return true iff all elements of iterable are true-ish
-    result = true
-    (result = false) for element in iterable when not element
-    return result
-
-window.any = (iterable) ->
-    # return true iff any element of iterable is true-ish
-    for element in iterable
-        if element
-            return true
-    return false
-
-window.utf8ToBase64 = (utf8) ->
-    return base64js.fromByteArray(TextEncoderLite.encode(utf8))
-
-window.base64ToUtf8 = (base64) ->
-    return TextDecoderLite.decode(base64js.toByteArray(base64))


create initial user record upon server startup

--- server/accounts.coffee
+++ server/accounts.coffee
@@ -1,3 +1,15 @@
 Accounts.emailTemplates.siteName = "EIDR-Connect"
+Accounts.emailTemplates.from = "EIDR-C <no-reply@eha.io>"

-Accounts.emailTemplates.from = "EIDR-C <no-reply@eha.io>"
+Meteor.startup ->
+  unless Meteor.users.find().count()
+    userData = Meteor.settings.private.initial_user
+    if userData
+      console.log "[ Creating initial user with email #{userData.email} … ]"
+      Accounts.createUser userData
+      newUserRecord = Meteor.users.findOne('emails.address': userData.email)
+      if newUserRecord
+        Roles.addUsersToRoles(newUserRecord._id, ['admin'])
+    else
+      console.warn '[ Meteor.settings.private.initial_user object \
+                          is required to create the initial user record ]'