JMachine: tidy up code, add some comment headers

--- workers/social/lib/social/models/computeproviders/machine.coffee
+++ workers/social/lib/social/models/computeproviders/machine.coffee
@@ -122,6 +122,64 @@
         assignedAt      : Date

+  # Helpers
+  # -------
+
+  generateSlugFromLabel = ({user, group, label, index}, callback)->
+
+    slug = if index? then "#{label}-#{index}" else label
+    slug = slugify slug
+
+    JMachine.count {
+      users : $elemMatch: id: user.getId()
+      groups: $elemMatch: id: group.getId()
+      slug
+    }, (err, count)->
+
+      return callback err  if err?
+
+      if count is 0
+        callback null, slug
+      else
+        index ?= 0
+        index += 1
+        generateSlugFromLabel {user, group, label, index}, callback
+
+
+  isOwner  = (user, machine) ->
+
+    userId = user.getId()
+
+    owner  = no
+    owner |= u.owner and u.id.equals userId  for u, i in machine.users
+
+    return owner
+
+
+  removeUser = (users, user)->
+
+    userId   = user.getId()
+    newUsers = []
+
+    for u in users
+      newUsers.push u  unless userId.equals u.id
+
+    return newUsers
+
+
+  addUser = (users, user, owner)->
+
+    newUsers = removeUser users, user
+    newUsers.push { id: user.getId(), owner }
+
+    return newUsers
+
+
+  # Private Methods
+  # ---------------
+
+  # Static Methods
+
   @create = (data, callback)->

     # JMachine.uid is a unique id which is generated from:
@@ -179,29 +237,66 @@
           callback null, machine

-  generateSlugFromLabel = ({user, group, label, index}, callback)->
-
-    slug = if index? then "#{label}-#{index}" else label
-    slug = slugify slug
-
-    JMachine.count {
-      users : $elemMatch: id: user.getId()
-      groups: $elemMatch: id: group.getId()
-      slug
-    }, (err, count)->
-
-      return callback err  if err?
-
-      if count is 0
-        callback null, slug
+  # Instance Methods
+
+  addUsers: (usersToAdd, owner, callback)->
+
+    users = @users.splice 0
+
+    for user in usersToAdd
+      users = addUser users, user, owner
+
+    @update $set: { users }, callback
+
+
+  removeUsers: (usersToRemove, callback)->
+
+    users = @users.splice 0
+
+    for user in usersToRemove
+      users = removeUser users, user
+
+    @update $set: { users }, callback
+
+
+  shareWith: (options, callback)->
+
+    { target, user, owner } = options
+    user  ?= yes
+    owner ?= no
+
+    unless target?
+      return callback new KodingError "Target required."
+
+    JUser = require '../user'
+    JName = require '../name'
+
+    JName.fetchModels target, (err, result)=>
+
+      if err or not result?
+        return callback new KodingError "Target not found."
+
+      targets = (target.models[0]  for target in result)
+
+      [ target ] = targets
+
+      if target instanceof JUser
+
+        if user
+        then @addUsers targets, owner, callback
+        else @removeUsers targets, callback
+
       else
-        index ?= 0
-        index += 1
-        generateSlugFromLabel {user, group, label, index}, callback
-
-
-
-  @one$: permit 'list machines',
+        callback new KodingError "Target does not support machines."
+
+
+
+  # Shared Methods
+  # --------------
+
+  # Static Methods
+
+  @one$ = permit 'list machines',

     success: revive

@@ -224,7 +319,7 @@
         callback err, machine

-  @some$: permit 'list machines',
+  @some$ = permit 'list machines',

     success: revive

@@ -242,16 +337,7 @@
         callback err, machines

-
-  isOwner  = (user, machine) ->
-
-    userId = user.getId()
-
-    owner  = no
-    owner |= u.owner and u.id.equals userId  for u, i in machine.users
-
-    return owner
-
+  # Instance Methods

   setProvisioner: permit 'set provisioner',

@@ -310,7 +396,7 @@
       daisy queue

-  setLabel: permit 'set domain',
+  setLabel: permit 'set label',

     success: revive

@@ -342,76 +428,6 @@
         @update $set: { label }, (err)-> kallback err, slug

-  removeUser = (users, user)->
-
-    userId   = user.getId()
-    newUsers = []
-
-    for u in users
-      newUsers.push u  unless userId.equals u.id
-
-    return newUsers
-
-
-  addUser = (users, user, owner)->
-
-    newUsers = removeUser users, user
-    newUsers.push { id: user.getId(), owner }
-
-    return newUsers
-
-
-  addUsers: (usersToAdd, owner, callback)->
-
-    users = @users.splice 0
-
-    for user in usersToAdd
-      users = addUser users, user, owner
-
-    @update $set: { users }, callback
-
-
-  removeUsers: (usersToRemove, callback)->
-
-    users = @users.splice 0
-
-    for user in usersToRemove
-      users = removeUser users, user
-
-    @update $set: { users }, callback
-
-
-  shareWith: (options, callback)->
-
-    { target, user, owner } = options
-    user  ?= yes
-    owner ?= no
-
-    unless target?
-      return callback new KodingError "Target required."
-
-    JUser = require '../user'
-    JName = require '../name'
-
-    JName.fetchModels target, (err, result)=>
-
-      if err or not result?
-        return callback new KodingError "Target not found."
-
-      targets = (target.models[0]  for target in result)
-
-      [ target ] = targets
-
-      if target instanceof JUser
-
-        if user
-        then @addUsers targets, owner, callback
-        else @removeUsers targets, callback
-
-      else
-        callback new KodingError "Target does not support machines."
-
-
   # .share can be used like this:
   #
   # JMachineInstance.shareWith { user: yes, owner: no, target: "gokmen"}, cb


Ensure current rank is used for the chart

--- resources/assets/coffee/react/profile-page/rank-chart.coffee
+++ resources/assets/coffee/react/profile-page/rank-chart.coffee
@@ -90,4 +90,6 @@
         x: data[0].x - 1
         y: data[0].y

+    _.last(data).y = -@props.stats.rank.global
+
     @rankChart.loadData data


return out of order events for consistency

--- services/real-time/app/coffee/EventLogger.coffee
+++ services/real-time/app/coffee/EventLogger.coffee
@@ -31,7 +31,7 @@
 		else
 			metrics.inc "event.#{channel}.out-of-order"
 			# logger.error {key:key, previous: previous, count:count, message:message}, "events out of order"
-			return # out of order
+			return "out-of-order"

 	_storeEventCount: (key, count) ->
 		previous = EVENT_LOG_COUNTER[key]


Add an english message for initialization

--- app/application.coffee
+++ app/application.coffee
@@ -40,7 +40,7 @@
     @addInitializer (options) =>
       # Initiate the translation
       resources = require 'internationalization/all'
-      i18n.init({resStore: resources, lng: config.lang}, (content)-> console.log('Traduction correctement initialisée.'))
+      i18n.init({resStore: resources, lng: config.lang}, (content)-> console.log('Translation correctly initialized.'))

     #Initialiation of backbone validation model,
     # the prototype of the model is extended in order to replace standard backbone validate method.


basic start on marking stones dead

basic start on marking stones dead

--- app/coffee/game.coffee
+++ app/coffee/game.coffee
@@ -127,6 +127,7 @@

 BoardAreaDom = React.createClass
   getInitialState: ->
+    deadStones: null
     proposedMove: null
     viewingMove: null

@@ -136,6 +137,7 @@
     $('.confirm_button').prop('disabled', true)
     setResponseSgf @props.initialSgf
     @setState
+      deadStones: null
       proposedMove: null
       viewingMove: newViewingMove

@@ -158,15 +160,22 @@
     objState = stateFromSgfObject sgfObject,
                                   moves: moveToShow,
                                   scoring: scoringMode
+                                  deadStones: @state.deadStones
     {boardState, lastPlayed, prisoners, scores} = objState
     size = parseInt(sgfObject.gameTrees[0].nodes[0].SZ, 10) or 19
     nextColor = null

-    if (tesuji_charm.onTurn and \
+    if scoringMode
+      onBoardClick = (xy) =>
+        groupToToggle = go_rules.groupPoints xy.x, xy.y, boardState
+        @setState deadStones: groupToToggle
+    else if (tesuji_charm.onTurn and \
         (@state.viewingMove == null or \
          @state.viewingMove == moveCount(origSgfObject)))
+      # ready to accept a normal (non-dead stone marking) move
       nextColor = nextPlayerInSgfObject origSgfObject
-      onPlaceStone = (xy) =>
+      onBoardClick = (xy) =>
+        return if boardState[xy.y][xy.x] != 'empty'
         # can't put the response form under React control since it needs to be
         # generated by WTForms, server-side
         $('.confirm_button').prop('disabled', false)
@@ -179,14 +188,14 @@
           x: xy.x
           y: xy.y
     else
-      onPlaceStone = ->
+      onBoardClick = ->

     {div} = React.DOM
     div {},
         [(React.createElement BoardDom,
                               boardState: boardState
                               hoverColor: nextColor
-                              placeStoneCallback: onPlaceStone
+                              clickCallback: onBoardClick
                               lastPlayed: lastPlayed
                               size: size),
          (React.createElement NavigationDom,
@@ -218,6 +227,8 @@
     dame = div {className: "territory neutral"}
     blackScore = div {className: "territory black"}
     whiteScore = div {className: "territory white"}
+    blackDead = div {className: "stone black dead"}
+    whiteDead = div {className: "stone white dead"}
     lastPlayedMarker = div {className: "last-played"}
     hover = if @props.hoverColor \
             then div {className: "placement #{@props.hoverColor}"} \
@@ -241,17 +252,18 @@
         when 'dame' then [dame]
         when 'blackscore' then [blackScore]
         when 'whitescore' then [whiteScore]
+        when 'blackdead' then [blackDead, whiteScore]
+        when 'whitedead' then [whiteDead, blackScore]
         else []
       if i == lastPlayed.x and j == lastPlayed.y
         result.push lastPlayedMarker
       result

     onClickForPos = (i, j) => (event) =>
-      if boardState[j][i] == 'empty'
-        @props.placeStoneCallback {
-          x: i
-          y: j
-        }
+      @props.clickCallback {
+        x: i
+        y: j
+      }

     div {className: 'goban'},
         for j in [0...size]
@@ -366,8 +378,10 @@

   If 'moves' is given as an option, include only that many moves.

-  If 'scoring' is given as an option, mark dame and scoring points."
-  {moves, scoring} = options
+  If 'scoring' is given as an option, mark dame and scoring points.
+
+  If 'deadStones' is given as an option, the listed stones should be dead."
+  {moves, scoring, deadStones} = options
   size = if sgfObject \
          then parseInt(sgfObject.gameTrees[0].nodes[0].SZ, 10) or 19 \
          else 19
@@ -402,6 +416,11 @@
       prisoners.white += result.captures.white
   if scoring
     [x, y] = [null, null]
+    if deadStones?
+      for [px, py] in deadStones
+        boardState[py][px] = switch boardState[py][px]
+          when 'black' then 'blackdead'
+          else boardState[py][px]
     {boardState, prisoners, scores} = scoreState {boardState, prisoners}
   else
     scores = null
@@ -415,7 +434,8 @@

   setRegionScores = (region, color) ->
     for [x, y] in region
-      boardState[y][x] = color
+      if boardState[y][x] == 'empty'
+        boardState[y][x] = color

   for region in getEmptyRegions boardState
     boundary = go_rules.boundingColor region, boardState


Added in functionality recommended in issue #1412

--- packages/rocketchat-oembed/server/server.coffee
+++ packages/rocketchat-oembed/server/server.coffee
@@ -31,7 +31,7 @@
 		parsedUrl: parsedUrl

 	request = httpOrHttps.request opts, Meteor.bindEnvironment (response) ->
-		if response.statusCode is 301 and response.headers.location?
+		if response.statusCode in [301,302,307] and response.headers.location?
 			request.abort()
 			console.log response.headers.location


Added loading statement to profile

Added loading statement to profile

--- app/profileItems.cjsx
+++ app/profileItems.cjsx
@@ -28,6 +28,7 @@
       </video>

+      {
Loading...
 if @state[@props.collection] is null}
       {items}


Fix string interpolation and abstract out multiple slide length calcs.

--- app/assets/javascripts/lib/components/slider.js.coffee
+++ app/assets/javascripts/lib/components/slider.js.coffee
@@ -30,16 +30,17 @@
       @current_slide = 1
       @$el = $(config.el)
       @$slides = @$el.find(config.slides)
+      @numSlides = @$el.find(@$slides).length

-      if @$el.length is 0 or @$slides.length < 2
+      if @$el.length is 0 or @numSlides < 2
         return false

       @$slides_container = @$el.find(config.slides_container)
       @$slides_viewport = @$el.find(config.slides_viewport)
       @$slider_controls = $('

')
       @$slider_pagination = $('

')
-      @$next = $('2 of '+@$el.find(@$slides).length+'')
-      @$prev = $(''+@$el.find(@$slides).length+' of '+@$el.find(@$slides).length+'')
+      @$next = $("2 of #{@numSlides}")
+      @$prev = $("#{@numSlides} of #{@numSlides}")
       @$legacy = $('html.ie7, html.ie8, body.browserIE7, body.browserIE8')

       if (@$slides_viewport.length is 0)
@@ -65,7 +66,7 @@
       pagination = ''

       @$slides.each (i) =>
-        pagination += '#{i+1}'
+        pagination += "#{i+1}"

       @$slider_pagination.append(pagination)


Only dismiss flash message if all are dismissible

--- app/assets/javascripts/neighborly/neighborly.js.coffee
+++ app/assets/javascripts/neighborly/neighborly.js.coffee
@@ -56,7 +56,7 @@
     init: ->
       clearTimeout(this.flash_time_out)
       if $('.flash').length > 0
-        this.flash_time_out = setTimeout(this.close, 5000) if $('.flash .alert-box').hasClass('dismissible')
+        this.flash_time_out = setTimeout(this.close, 5000) if $('.flash .alert-box.dismissible').length == $('.flash .alert-box').length
         $('.flash a.close').click(this.close)

     close: ->


bugfix

bugfix

--- apps/game/routes/chat.coffee
+++ apps/game/routes/chat.coffee
@@ -12,6 +12,7 @@
       return next(err) if err?
       res.locals.moment = moment
       res.render 'chat',
+        charater: req.character
         messages: messages
         page: page
         page_size: page_size


fixed tests.

fixed tests.

--- test/specs/providerSpec.coffee
+++ test/specs/providerSpec.coffee
@@ -66,13 +66,6 @@
             expect
                 default: []
             .toEqual $builder.forms
-
-
-    describe '$builderProvider.formsId', ->
-        it '$builderProvider.formsId', ->
-            expect
-                default: 0
-            .toEqual builderProvider.formsId

     # ----------------------------------------
@@ -173,7 +166,7 @@
                 component: 'inputText'

             expect
-                id: 0
+                id: undefined
                 component: 'inputText'
                 editable: yes
                 index: 0
@@ -201,7 +194,7 @@
                 validation: '/.*/'

             expect
-                id: 0
+                id: undefined
                 component: 'inputText'
                 editable: no
                 index: 0
@@ -299,11 +292,6 @@
             $builder.registerComponent 'inputText',
                 template: "

"
                 popoverTemplate: "

"
-
-        it '$builder.insertFormObject() a new form', inject ($builder) ->
-            $builder.insertFormObject 'form', 0, component: 'inputText'
-            expect(builderProvider.forms.form.length).toBe 1
-            expect(builderProvider.formsId.form).toBe 1

         it '$builder.insertFormObject() index out of bound', inject ($builder) ->
             spyOn(builderProvider.forms.default, 'splice').and.callFake (index, length) ->


Don't process malformed messages.

Don't process malformed messages.

--- dashboard/assets/javascripts/models.js.coffee
+++ dashboard/assets/javascripts/models.js.coffee
@@ -83,6 +83,7 @@
     # Sanity-check the message.
     console.log 'Message is malformed (no ident)' unless ident?
     console.log 'Message is malformed (no type identifier)' unless type?
+    return unless ident? && type?

     # Do we have a job for the identifier?
     # If we don't, register a job and retry processing when the run loop


Remove test

Remove test

--- public/javascripts/app/views/profile/list.coffee
+++ public/javascripts/app/views/profile/list.coffee
@@ -94,7 +94,6 @@
       url: "/cities"
       dataType: "json"
       success: (cities)=>
-        $.l cities
         $location = @$("input[name='location']")
         $location.autocomplete cities,
           autoFill: false


Add physicalBounds to the section - this is the physical coordinates of the section area on the original image

--- app/models/section.coffee
+++ app/models/section.coffee
@@ -5,6 +5,7 @@
   sequences: DS.hasMany 'sequence'
   translation: DS.attr 'markdown'
   bounds: DS.attr 'rect'
+  physicalBounds: DS.attr 'rect'
   state: DS.attr 'string'
   createdAt: DS.attr 'date'

@@ -13,4 +14,4 @@
     translation.substr 0, 100
   ).property('translation')

-`export default Section`+`export default Section`


cache popped value

cache popped value

--- src/cleaner/cleaner.coffee
+++ src/cleaner/cleaner.coffee
@@ -14,7 +14,8 @@
   for char in line.split ''
     if state is quoting
       if escape_stack.length
-        clean += escape_stack.pop() + char
+        prev = escape_stack.pop()
+        clean += prev + char
       else
         switch char
           when '\\'
@@ -26,8 +27,8 @@
             clean += char
     else if state is commenting
       if escape_stack.length
+        prev = escape_stack.pop()
         if char is '/'
-          escape_stack.pop()
           state = ready
           clean += '  '
         else
@@ -39,19 +40,18 @@
           clean += ' '
     else # if state is ready
       if escape_stack.length
+        prev = escape_stack.pop()
         switch char
           when '*'
-            escape_stack.pop()
             clean += '  '
             state = commenting
           when '/'
-            escape_stack.pop()
             return clean
           when '\''
-            clean += escape_stack.pop() + char
+            clean += prev + char
             state = quoting
           else
-            clean += escape_stack.pop() + char
+            clean += prev + char
       else
         switch char
           when '/'


Added three new surveys

Added three new surveys

--- app/models/subject.coffee
+++ app/models/subject.coffee
@@ -29,6 +29,18 @@
       id: Config.surveys.ferengi.id
       workflowId: Config.surveys.ferengi.workflowId
       tree: FerengiTree
+    candels_2epoch:
+      id: Config.surveys.candels_2epoch.id
+      workflowId: Config.surveys.candels.workflowId
+      tree: CandelsTree
+    goods_full:
+      id: Config.surveys.goods_full.id
+      workflowId: Config.surveys.ferengi.workflowId
+      tree: FerengiTree
+    sloan_singleband:
+      id: Config.surveys.sloan_singleband.id
+      workflowId: Config.surveys.sloan.workflowId
+      tree: SloanTree

   @url: (params) -> @withParams "/projects/galaxy_zoo/groups/#{ params.surveyId }/subjects", limit: params.limit
   @randomSurveyId: ->


showProto option

showProto option

--- app/object_browser.coffee
+++ app/object_browser.coffee
@@ -3,7 +3,7 @@

 Toggleable =
   getInitialState: ->
-    open: @props.initially_open or false
+    open: @props.initiallyOpen or false
   toggle: (e) ->
     e.stopPropagation()
     @setState open: !@state.open
@@ -15,14 +15,16 @@

 ObjectBrowserComponent = React.createClass
   displayName: 'ObjectBrowserComponent'
+  getDefaultProps: ->
+    showProto: true
   render: ->
     React.DOM.div {className: 'object-browser'},
       if isSimple @props.object
         componentForObject @props.object
       else if _.isArray @props.object
-        ObjectBrowserTopLevelArrayComponent object: @props.object
+        ObjectBrowserTopLevelArrayComponent @props
       else
-        ObjectBrowserTopLevelObjectComponent object: @props.object
+        ObjectBrowserTopLevelObjectComponent @props

 ObjectBrowserTopLevelObjectComponent = React.createClass
   displayName: 'ObjectBrowserTopLevelObjectComponent'
@@ -36,7 +38,7 @@
         React.DOM.div {},
           # spacer lol
           React.DOM.i {className: "fa fa-fw"}
-          ObjectBrowserEntriesComponent object: @props.object
+          ObjectBrowserEntriesComponent object: @props.object, showProto: @props.showProto

 ObjectBrowserTopLevelArrayComponent = React.createClass
@@ -51,7 +53,7 @@
         React.DOM.div {},
           # spacer lol
           React.DOM.i {className: "fa fa-fw"}
-          ObjectBrowserEntriesComponent object: @props.object
+          ObjectBrowserEntriesComponent object: @props.object, showProto: @props.showProto

 OneLineArrayComponent = React.createClass
   displayName: 'OneLineArrayComponent'
@@ -104,7 +106,7 @@
         ObjectBrowserEntryComponent {key, value, enumerable}
       if props.length > @state.visibleEntries
         React.DOM.div {onClick: @expand, className: 'run-button'}, 'Show more'
-      if proto?
+      if proto? and @props.showProto
         ObjectBrowserEntryComponent {key: '__proto__', value: proto, own: false}

 isSimple = (o) ->
@@ -143,7 +145,7 @@
           React.DOM.div {},
             # spacer lol
             React.DOM.i {className: "fa fa-fw"}
-            ObjectBrowserEntriesComponent object: value
+            ObjectBrowserEntriesComponent object: value, showProto: @props.showProto

 componentForObject = (o) ->
   if _.isUndefined o


random name for elements

random name for elements

--- app/scripts/editor.coffee
+++ app/scripts/editor.coffee
@@ -14,24 +14,34 @@
 		return if el[0].nodeType == 3 then el.parent() else el
 	clearStatus: ->
 		$('.debug-status').addClass 'hidden'
-		status.cmd = status.ctrl = status.alt = status.shift = status.empty = false
+		@status.cmd = @status.ctrl = @status.alt = @status.shift = @status.empty = false
+	rand: (len=4)->
+		Math.random().toString(36).substr(2,len).toUpperCase()
 	update: ->
+		self = @
+		$('p,h1,pre').not('[name]').each ->
+			$(@).attr 'name', self.rand()
 		$('#debug').text $('#editor').html()
+	toggleFormatBlock: (tag)->
+		el = @getSelectedElement()
+		console.log el
+		if el.is tag
+			(new Command('formatBlock', 'p') ).run()
+		else
+			(new Command('formatBlock', tag) ).run()
 	handleKeyDown: (e)->
 		# debug
 		$('#debug-keydown').text e.keyCode

 		switch e.keyCode
 			when 49
-				if @status.cmd || @status.ctrl
-					# document.execCommand('formatBlock', false, 'h1')
-					el = @getSelectedElement()
-					console.log 'selected el'
-					console.log el
-					if el.is 'h1'
-						(new Command('formatBlock', 'p') ).run()
-					else
-						(new Command('formatBlock', 'h1') ).run()
+				if @status.cmd or @status.ctrl
+					@toggleFormatBlock 'h1'
+					e.preventDefault()
+					e.stopPropagation()
+			when 50
+				if @status.cmd or @status.ctrl
+					@toggleFormatBlock 'pre'
 					e.preventDefault()
 					e.stopPropagation()
 			when 16


set default url when click link button

set default url when click link button

--- app/scripts/editor.coffee
+++ app/scripts/editor.coffee
@@ -147,6 +147,9 @@
 				@showToolpadOverSelection 'toolpad'
 				@restoreSelection 'toolpad'
 				@linkRange = @selection().getRangeAt 0
+				unless @getSelectedElement().is 'a'
+					@applyOrCancelUrl @linkRange, '#'
+					@linkRange = @selection().getRangeAt 0
 			else
 				@hideToolpad()
 	showToolpadOverSelection: (prefix='m')->
@@ -177,6 +180,7 @@
 	cancelLinkInput: ->
 		$('#toolpad li:first-child').removeClass 'toggled'
 	applyOrCancelUrl: (range, url)->
+		console.log 'applyOrCancelUrl'
 		@selection().removeAllRanges()
 		@selection().addRange range
 		if url
@@ -237,7 +241,8 @@
 				self.hideToolpad()
 		$('#toolpad').delegate '.cancel', 'click', (e)->
 			self.cancelLinkInput()
-			self.applyOrCancelUrl self.linkRange, $(@).val() if self.linkRange
+			if self.linkRange
+				self.applyOrCancelUrl self.linkRange, $(@).val()

 	update: ->
 		self = @


Add find by uid

Add find by uid

--- app/src/base/model.coffee
+++ app/src/base/model.coffee
@@ -145,6 +145,11 @@
     object._find = {_id: id, __uid: ledger.storage.local.createUniqueObjectIdentifier(@name, id)[1]}
     object

+  @findByUid: (uid) ->
+    object = new @()
+    object.__uid = uid
+    object
+
   @getCollectionName: () -> _.pluralize(_.str.underscored(@name))

   # Creates a relationship object from a store object or array reference. This fucntion is private to this file


test if clicks are in piece's current rendered bounding box

--- views/puzzle.coffee
+++ views/puzzle.coffee
@@ -39,7 +39,7 @@
     @show_clicks = true

-  click_handle: (e) ->
+  handle_mousedown: (e) ->
     @canvas = document.getElementById("puzzle-widget")
     dx = @canvas.offsetLeft
     dy = @canvas.offsetTop
@@ -50,6 +50,9 @@

     console.log("mouse click: "+x+","+y)
     @puzzle.pixel_test.big_dot(x,y) if @show_clicks == true
+
+    console.log("MOUSEDOWN within piece bounding box") if @puzzle.piece.in_bounding_box(x,y)
+
     hex = @piece_drag.get_piece_hex_position(x,y)
     console.log("A*,B* ~= "+hex[0]+","+hex[1])
     @puzzle.piece.draw_piece_ab(hex[0],hex[1])
@@ -62,6 +65,7 @@
     @puzzle = puzzle_app
     @hex_box = @puzzle.hex_box
     @grid_model = @puzzle.grid_model
+    @drag_active = false

   get_piece_hex_position: (x,y) ->
@@ -74,7 +78,6 @@
     wd_ht = @hex_box.get_box_size()
     dx = Math.floor(wd_ht[0]/2) - 10
     dy = Math.floor(wd_ht[1]/2) - 10
-    console.log("piece center to anchor hex offset = "+dx+","+dy)
     return [dx,dy]

@@ -156,6 +159,7 @@
     @piece_mask  = new PiecePattern(this)
     @redraw = new PieceRedrawBuffer(this)
     @hexes = []
+    @bounding_box = [0,0,0,0,]

   construct_piece: (sym) ->
@@ -163,8 +167,9 @@
     @hexes = @get_hexes()
     @hex_box.set_hex_box(sym)
     wd_ht = @hex_box.get_box_size()
-    @redraw.reset_size(wd_ht[0],wd_ht[1])
-
+    @width = wd_ht[0]
+    @height = wd_ht[1]
+    @redraw.reset_size(@width,@height)
     @piece_mask.draw_piece_pattern()
     @cut_piece_from_photo()
     @draw_piece_ab(-7,5)
@@ -200,6 +205,22 @@
   draw_piece: (x,y) ->
     context = @puzzle.puzzle_view.context_canvas
     context.drawImage(@piece_mask.img,x,y)
+    @reset_bounding_box(x,y)
+
+
+  # TODO This 'bounding box' thing might be something that we'll eventually
+  #      want to break out into a separate class representing the rendering
+  #      location of the piece as it is drawn in different locations across
+  #      the grid.
+  reset_bounding_box: (x,y) ->
+    @left = x
+    @right = x + @width
+    @top = y
+    @bottom = y + @height
+
+
+  in_bounding_box: (x,y) ->
+    if x>@left && x<@right && y>@top && y<@bottom then return true else return false

   # TODO Does this method belong in PuzzlePattern class?
@@ -352,20 +373,13 @@
     aa = Math.floor((x-12)/14)-7
     if aa%2 != 0 then odd = 1 else odd = 0
     bb = Math.floor((y-9*odd+2.5)/20)-1
-    console.log("ab = "+[aa,bb])
     corner = @puzzle.hex_box.get_box_xy_ab(aa,bb)
-    console.log("corner = "+corner)
     ctr_x = corner[0]+9
     ctr_y = corner[1]+10
-    console.log("center = "+ctr_x+","+ctr_y)
     dx = Math.abs(x-ctr_x)
     dy = Math.abs(y-ctr_y)
-    console.log("dx = "+dx)
-    console.log("dy = "+dy)
     r2 = dx*dx+dy*dy
-    console.log("r2 = "+r2)
     in_bounds = false if r2>67 #(if radius > 8.2)
-
     hex = [aa,bb] if (in_bounds == true)
     return hex

@@ -691,7 +705,7 @@

 @mousedown = (e) ->
-  @app.events.click_handle(e)
+  @app.events.handle_mousedown(e)

 start = () ->


Wait to remove upload until asset creation completes

--- web/volume/assist.coffee
+++ web/volume/assist.coffee
@@ -41,6 +41,7 @@
       remove = (file) ->
         $scope.uploads.remove(file)
         $scope.progress = file.flowObj.progress()
+        return

       $scope.fileAdded = (file) ->
         $scope.uploads.push(file)
@@ -76,9 +77,8 @@
                 report: res
                 owner: form
               file.cancel()
-              file.progressValue = null
+              remove(file)
               return
-        remove(file)
         return

       $scope.fileProgress = (file) ->


Remove unneeded code

Remove unneeded code

--- assets/scripts/travis.coffee
+++ assets/scripts/travis.coffee
@@ -192,9 +192,6 @@
 Ember.LinkView.reopen
   loadingClass: 'loading_link'

-if Travis.config.pro
-  $('body').addClass('pro')
-
 if charm_key = $('meta[name="travis.charm_key"]').attr('value')
   @__CHARM =
     key: $('meta[name="travis.charm_key"]').attr('value')


Initial modifications to sentence selection to use a hashmap for object selection

--- annotran/static/scripts/annotator/plugin/sentenceselection.coffee
+++ annotran/static/scripts/annotator/plugin/sentenceselection.coffee
@@ -2,6 +2,53 @@
 $ = Annotator.$
 xpathRange = Annotator.Range
 Util = Annotator.Util
+
+
+`
+function HashTable() {
+    this.hashes = {};
+}
+
+HashTable.prototype = {
+    constructor: HashTable,
+
+    put: function( key, value ) {
+        this.hashes[ $(key).getPath() ] = value;
+    },
+
+    get: function( key ) {
+        return this.hashes[ $(key).getPath() ];
+    }
+};
+
+jQuery.fn.extend({
+    getPath: function () {
+        var path, node = this;
+        while (node.length) {
+            var realNode = node[0], name = realNode.localName;
+            if (!name) break;
+            name = name.toLowerCase();
+
+            var parent = node.parent();
+
+            var sameTagSiblings = parent.children(name);
+            if (sameTagSiblings.length > 1) {
+                allSiblings = parent.children();
+                var index = allSiblings.index(realNode) + 1;
+                if (index > 1) {
+                    name += ':nth-child(' + index + ')';
+                }
+            }
+
+            path = name + (path ? '>' + path : '');
+            node = parent;
+        }
+
+        return path;
+    }
+});
+`
+

 # This plugin implements the UI code for selecting sentences by clicking
 module.exports = class SentenceSelection extends Annotator.Plugin
@@ -12,8 +59,8 @@
       "click": @findASentence
     })

+    this.currentIndexes = new HashTable()
     this.operational = false
-    this.currentIndex = 0
     this.currentSentence = 0
     this.storedEvent = null
     this.extentElement = null
@@ -84,8 +131,9 @@
     # here want to test:
     # 1. is there a sibling element?
     # 2. is there a parent element with a next sibling?
-    this.currentIndex = 0
+    this.currentIndexes.put(currentTarget, 0)
     this.currentSentence = 0
+

     nextSibling = this.returnNext(currentTarget)

@@ -186,7 +234,7 @@
         if desiredText == undefined
           # Just start in the next jump element without saving the current position in the document
           nextSibling = this.returnNext(currentTarget)
-          this.currentIndex = 0
+          this.currentIndexes.put(currentTarget, 0)
           this.currentSentence = 0
           this.selectSentence nextSibling
         else if desiredText.endsWith(".") or desiredText.endsWith("!") or desiredText.endsWith("?")
@@ -203,10 +251,11 @@

     this.storedEvent = event

-    this.currentIndex = 0
+
+    event.target = this.normalizeStyleTags event.target
+
+    this.currentIndexes.put(event.target, 0)
     this.currentSentence = 0
-
-    event.target = this.normalizeStyleTags event.target

     this.selectSentence event.target

@@ -224,6 +273,9 @@

   findNextJumpNode: (target) ->
     parent = $(target).parent()
+
+    if this.currentIndexes.get(parent) > 0
+      return parent

     nextSibling = $(parent).next()

@@ -259,16 +311,16 @@

     currentSelection = window.getSelection()

-    this.currentIndex = currentSelection.extentOffset
-    this.currentSentence = this.currentSentence + 1
-
     elementToUse = currentSelection.extentNode.parentElement
     tagName = $(elementToUse).prop('tagName').toLowerCase()

-    if elementToUse.textContent.length <= (this.currentIndex)
+    this.currentIndexes.put(elementToUse, currentSelection.extentOffset)
+    this.currentSentence = this.currentSentence + 1
+
+    if elementToUse.textContent.length <= (this.currentIndexes.get(elementToUse))
       nextSibling = $(elementToUse).next()

-      this.currentIndex = 0
+      this.currentIndexes.put(nextSibling, 0)
       this.currentSentence = 0

       # algorithm here is:


Test relative time or date

Test relative time or date

--- test/javascripts/unit/relative_date_test.js.coffee
+++ test/javascripts/unit/relative_date_test.js.coffee
@@ -7,10 +7,27 @@

   equal getText(el), now.format("MMM D")

-
 test "last year", ->
   before = moment().subtract("years", 1).subtract("days", 1)
   el = addTimeEl type: "date", datetime: before.toISOString()
   run()

   equal getText(el), before.format("MMM D, YYYY")
+
+
+module "relative time or date"
+
+
+test "today", ->
+  now = moment()
+  el = addTimeEl type: "time-or-date", datetime: now.toISOString()
+  run()
+
+  equal getText(el), now.format("h:mma")
+
+test "before today", ->
+  before = moment().subtract("days", 1)
+  el = addTimeEl type: "time-or-date", datetime: before.toISOString()
+  run()
+
+  equal getText(el), before.format("MMM D")


Fix bug indeoOf for IE8 in coffee script

--- backbone.JJRelational.coffee
+++ backbone.JJRelational.coffee
@@ -571,7 +571,7 @@
         # trigger early if necessary
         triggerChanges()
         # remove from changes to prevent triggering it twice
-        i = changes.indexOf @.idAttribute
+        i = _.indexOf(changes, @.idAttribute)
         if ~i then changes.splice i, 1
         delete attrs[@.idAttribute]


minor tweak to google maps instantiation

--- app/assets/javascripts/main.coffee
+++ app/assets/javascripts/main.coffee
@@ -348,7 +348,10 @@
             google.maps.event.addListener this._map, "bounds_changed", this._navigateDebounce

             # initialize autocomplete widget
-            this._autocomplete = new google.maps.places.Autocomplete(document.getElementById("address"))
+            this._autocomplete = new google.maps.places.Autocomplete(
+            	document.getElementById("address"),
+            	{componentRestrictions: {country: 'us'}}
+            )
             google.maps.event.addListener this._autocomplete, 'place_changed', this._navigateToAddress
         else
             this._map.panTo mapOptions.center
@@ -412,7 +415,7 @@
 $(document).ready(() ->
    s = document.createElement("script")
    s.type = "text/javascript"
-   s.src  = "https://maps.google.com/maps/api/js?libraries=places&callback=gmap_draw"
+   s.src  = "https://maps.google.com/maps/api/js?v=3.exp&sensor=true&libraries=places,geometry&callback=gmap_draw"
    window.gmap_draw = () ->
        window.app = new MainController
        window.app.start()


Fix data-ts references

Fix data-ts references

--- app/assets/javascripts/main.coffee
+++ app/assets/javascripts/main.coffee
@@ -10,7 +10,7 @@
   selector.addClass('foobar')

 fooBarAdder = (arg1, msg) ->
-  foobarize($('data-ts="' + msg.ts + '"]'))
+  foobarize($('[data-ts="' + msg.ts + '"]'))

 TS.groups.message_received_sig.add(fooBarAdder)
 TS.channels.message_received_sig.add(fooBarAdder)
@@ -27,7 +27,7 @@
       $(el).addClass 'bot'

 botAdder = (arg1, msg) ->
-  botify($('data-ts="' + msg.ts + '"]'))
+  botify($('[data-ts="' + msg.ts + '"]'))

 botifyMessages = =>
   botify($('.message'))


ensure document results are in version order

--- app/coffee/MongoPackManager.coffee
+++ app/coffee/MongoPackManager.coffee
@@ -59,6 +59,9 @@
 			return false if rangeQuery?['$lt']? && item['v'] >= rangeQuery['$lt']
 			return false if rangeQuery?['$gt']? && item['v'] <= rangeQuery['$gt']
 			return true
+
+		versionOrder = (a, b) ->
+			b.v - a.v

 		# create a query which can be used to select the entries BEFORE
 		# the range because we sometimes need to find extra ones (when the
@@ -97,16 +100,16 @@
 					.limit(1)
 				extra.toArray (err, result2) ->
 					if err?
-						return callback err, updates
+						return callback err, updates.sort versionOrder
 					else
 						extraSet = MongoPackManager._unpackResults(result2)
 						updates = MongoPackManager._filterAndLimit(updates, extraSet, filterFn, limit)
-						callback err, updates
+						callback err, updates.sort versionOrder
 				return
 			if err?
 				callback err, result
 			else
-				callback err, updates
+				callback err, updates.sort versionOrder

 	findProjectResults: (collection, query, limit, callback) ->
 		# query - the mongo query selector, includes both the doc_id/project_id and


Subject Viewer: fix inFlipbookMode (#5993)

Check `props.allowFlipbook` and `isGroupSubject` before enabling flipbook mode in the subject viewer.

--- app/components/subject-viewer.cjsx
+++ app/components/subject-viewer.cjsx
@@ -102,12 +102,13 @@

   componentDidUpdate: (prevProps) ->
     isGroupSubject = @props.subject?.metadata?['#subject_group_id']?
+    allowFlipbook = @props.allowFlipbook || isGroupSubject
     if @props.subject isnt prevProps.subject
       # turn off the slideshow player and reset any counters
       @setPlaying false
       @setState
         frame: @getInitialFrame()
-        inFlipbookMode: !isGroupSubject
+        inFlipbookMode: allowFlipbook
         isGroupSubject: isGroupSubject

   logSubjClick: (logType) ->


Options are now documented in README.md

Options are now documented in README.md

--- reactive-selectize.coffee
+++ reactive-selectize.coffee
@@ -5,18 +5,6 @@
 # This class integrates selectize.js controls with Meteor's reactive data
 # sources. It specifically implements synchronizing Mongo.Cursor instances,
 # but can also handle the results of arbitrary reactive computations.
-
-# Selectize option extensions:
-#   options      required, function returning either a Mongo.Cursor or an array
-#   placeholder  optional, an object like your options but without value
-#   selected     optional, an array of values that are selected
-#   valueField   optional, defaults to "value"
-#   labelField   optional, defaults to "label"
-
-# remotePersist:
-# never -> never persist, always delete & deselect options when deleted remotely
-# selected -> default, only persist if option is selected
-# always -> keep all remotely deleted options

 # TODO Option groups
 # TODO Reactive placeholder (for localization)


Environments: Define stacks array if empty.

--- client/Environments/views/environmentsmainscene.coffee
+++ client/Environments/views/environmentsmainscene.coffee
@@ -48,10 +48,11 @@
   fetchStacks: (callback)->

     EnvironmentDataProvider.get (@environmentData) =>
-      console.clear()
+      # console.clear()
       # log "environment data", @environmentData
+
       {JStack} = KD.remote.api
-      JStack.getStacks (err, stacks)=>
+      JStack.getStacks (err, stacks = [])=>
         warn err  if err

         group = KD.getGroup().title


open sesame (or, "I brought back tooltips")

--- app/javascript/packs/legacy.coffee
+++ app/javascript/packs/legacy.coffee
@@ -88,4 +88,5 @@
 $(document).on 'turbolinks:load', _ready

 $(document).on 'turbolinks:render', ->
-  $('.dropdown-toggle').dropdown()+  $('.dropdown-toggle').dropdown()
+  $('[data-toggle="tooltip"]').tooltip()


Updated tests to weaken "number" input field requirement, which isn't supported in Firefox.

--- cms/static/coffee/spec/views/metadata_edit_spec.coffee
+++ cms/static/coffee/spec/views/metadata_edit_spec.coffee
@@ -113,6 +113,13 @@

             verifyEntry = (index, display_name, type) ->
                 expect(childModels[index].get('display_name')).toBe(display_name)
+
+                # Some browsers (e.g. FireFox) do not support the "number"
+                # input type.  We can accept a "text" input instead
+                # and still get acceptable behavior in the UI.
+                if type == 'number' and childViews[index].type != 'number'
+                    type = 'text'
+
                 expect(childViews[index].type).toBe(type)

             verifyEntry(0, 'Display Name', 'text')
@@ -164,6 +171,13 @@
     assertInputType = (view, expectedType) ->
         input = view.$el.find('.setting-input')
         expect(input.length).toEqual(1)
+
+        # Some browsers (e.g. FireFox) do not support the "number"
+        # input type.  We can accept a "text" input instead
+        # and still get acceptable behavior in the UI.
+        if expectedType == 'number' and input[0].type != 'number'
+            expectedType = 'text'
+
         expect(input[0].type).toEqual(expectedType)

     assertValueInView = (view, expectedValue) ->


on being more coffee-like

on being more coffee-like

--- web/astrolin.coffee
+++ web/astrolin.coffee
@@ -19,7 +19,7 @@
 # The /public is ecstatic.  This `ecstasy` can be invoked explicitly further on.
 ecstatic_opts = autoIndex: false
 ecstatic_opts.cache = if dev then false else true
-ecstasy = ecstatic(app_path + '/public', ecstatic_opts)
+ecstasy = ecstatic app_path + '/public', ecstatic_opts

 app.helpers
@@ -99,7 +99,7 @@
   app.set 'views', app_path + '/views'
   app.register '.html', jade
   app.set 'view engine', 'html'
-  app.set 'view options', { layout: yes }
+  app.set 'view options', layout: yes

   # Router after any other assets.
   app.use app.router
@@ -107,29 +107,29 @@

 # Home page
 app.get "/", (req, res, next) ->
-  res.render "index", { title: "Welcome" }
+  res.render "index", title: "Welcome"

 # Projects JSON
 app.get "/projects", (req, res, next) ->
   res.contentType('application/json')
-  res.render("projects", {layout: false})
+  res.render "projects", layout: no

 # Project page
 app.get "/to/:project?", (req, res, next) ->
   req.params.project = "lin" unless req.params.project?
-  res.render "project", { title: req.params.project
-                        , headest: ""
-                        , project: req.params.project
-                        , forehead: "
" + req.params.project.toUpperCase()
-                        }
+  res.render "project",
+    title: req.params.project
+    headest: ""
+    project: req.params.project
+    forehead: "
" + req.params.project.toUpperCase()

 # Projects per category
 app.get "/cat/:category", (req, res, next) ->
-  res.render "category", { title: req.params.category
-                        , headest: ""
-                        , category: req.params.category
-                        , forehead: "
" + req.params.category.toLowerCase()
-                        }
+  res.render "category",
+    title: req.params.category
+    headest: ""
+    category: req.params.category
+    forehead: "
" + req.params.category.toLowerCase()

 # Catch-all: not found


remove unused requires

remove unused requires

--- tutor/src/components/new-course/select-course.cjsx
+++ tutor/src/components/new-course/select-course.cjsx
@@ -1,12 +1,10 @@
 React = require 'react'
 BS = require 'react-bootstrap'

-sortBy  = require 'lodash/sortBy'
+first   = require 'lodash/first'
 partial = require 'lodash/partial'
 isEqual = require 'lodash/isEqual'
 isEmpty = require 'lodash/isEmpty'
-first   = require 'lodash/first'
-classnames = require 'classnames'

 {NewCourseActions, NewCourseStore} = require '../../flux/new-course'
 TutorRouter = require '../../helpers/router'


UpdateRankings() in newLead()

UpdateRankings() in newLead()

--- server.coffee
+++ server.coffee
@@ -588,7 +588,6 @@
 	Db.shared.modify 'game', 'teams', teamClient, 'users', client, 'userScore', (v) -> v + points
 	Db.shared.modify 'game', 'teams', teamClient, 'teamScore', (v) -> v + points
 	# new lead check
-	updateTeamRankings()
 	checkNewLead(teamClient)

 refreshInrangeTimer = (client, device) ->
@@ -619,6 +618,9 @@
 		Db.shared.set 'game', 'firstTeam', teamMax # store firstTeam for next new team calculation
 		pushToTeam(teamMax, "Your team took the lead!")
 		pushToRest(teamMax, "Team " + Db.shared.peek('colors', teamMax, 'name') + " took the lead!")
+	# Update rankings
+	updateTeamRankings()
+

 # Sends a push notification, message, to all team members
 pushToTeam = (teamId, message) ->


Some minor changes

Some minor changes

--- server.coffee
+++ server.coffee
@@ -8,10 +8,10 @@

 server = new Hapi.Server

-server.connection
+server.connection
   host: config.host
   port: config.port
-
+
 requirer = new Requirer
 requirer.getDirectoryFiles __dirname + '/config/hapi', (error, file) =>
   return console.error error if error?
@@ -22,4 +22,4 @@
   require(file)(server, config)

 server.start =>
-  console.log "#{colors.green(config.app.name)} running at: #{colors.cyan(server.info.uri)}"+  console.log "#{colors.green(config.app.name)} running at: #{colors.cyan(server.info.uri)}"


Pull announcements from our Trello board.

--- server.coffee
+++ server.coffee
@@ -15,8 +15,10 @@
 zmq = require('zmq')
 cors = require('cors')
 async = require('async');
-nodemailer = require('nodemailer');
-moment = require('moment');
+nodemailer = require('nodemailer')
+moment = require('moment')
+trello = require('node-trello')
+cache = require('memory-cache')

 # MongoDB connection
 appName = 'netrunner'
@@ -430,11 +432,20 @@
     res.json(200, (d.username or d.name for d in data))

 app.get '/data/news', (req, res) ->
-  db.collection('news').find().sort({_id: -1}).toArray (err, data) ->
-    for d in data
-      d.date = moment(d._id.getTimestamp().toISOString()).format("MM/DD/YYYY HH:mm");
-      delete d._id
-    res.json(200, data)
+  cached = cache.get('news')
+  if not cached
+    t = new trello(process.env['TRELLO_API_KEY'])
+    t.get '/1/lists/5668b498ced988b1204cae9a/cards', {filter : 'open', fields : 'dateLastActivity,name'}, (err, data) ->
+      throw err if err
+      for d in data
+        d.date = moment(d.dateLastActivity).format("MM/DD/YYYY HH:mm")
+        d.title = d.name
+        delete d.id
+        delete d.name
+      cache.put('news', data, 60000) # 60 seconds timeout
+      res.json(200, data)
+  else
+    res.json(200, cached)

 app.get '/data/:collection', (req, res) ->
   db.collection(req.params.collection).find().sort(_id: 1).toArray (err, data) ->


experiment

experiment

--- server.coffee
+++ server.coffee
@@ -471,7 +471,7 @@

 handlePushWebHook = (req, res) ->
   payload  = req.body
-  console.log "Hook was called", payload, req.params.community
+  console.log "Hook was called", payload, req.params.community, req
   topic = payload.ref.split("/")[2]
   channel = req.params.community
   payload.topic = topic


Fix capture and neutralize timers

Fix capture and neutralize timers

--- server.coffee
+++ server.coffee
@@ -39,6 +39,8 @@
 	if version < 3 # Change yellow color
 		newVersion = 3
 		initializeColors()
+	# TODO change inRange to new layout
+

 	# Write new version to the database
 	if newVersion isnt version
@@ -193,8 +195,8 @@
 			newStatus = beaconDistance < beaconRadius
 			if newStatus != current
 				# Cancel timers of ongoing caputes/neutralizes (new ones will be set below if required)
-				Timer.cancel 'onCapture', {beacon: beacon.key(), players: getInrangePlayers(beacon.key())}
-				Timer.cancel 'onNeutralize', {beacon: beacon.key(), players: getInrangePlayers(beacon.key())}
+				Timer.cancel 'onCapture', {beacon: beacon.key()}
+				Timer.cancel 'onNeutralize', {beacon: beacon.key()}
 				removed = undefined;
 				if newStatus
 					if not device? # Deal with old clients by denying them to be added to inRange
@@ -360,7 +362,7 @@
 exports.onNeutralize = (args) ->
 	beacon = Db.shared.ref 'game', 'beacons', args.beacon
 	neutralizer = beacon.peek('nextOwner')
-	inRangeOfTeam = getInrangePlayersOfTeamArray(args.beacon, nextOwner)
+	inRangeOfTeam = getInrangePlayersOfTeamArray(args.beacon, neutralizer)
 	log '[onNeutralize()] Team ', neutralizer, ' has neutralized beacon ', beacon.key(), ', players: '+inRangeOfTeam
 	beacon.set 'percentage', 0
 	beacon.set 'owner', -1


Remove Content-Length header

Remove Content-Length header

--- server.coffee
+++ server.coffee
@@ -35,6 +35,7 @@

           else
             headers = coffee.helpers.extend({'Cache-Control': 'public'}, imageResponse.headers)
+            delete headers['content-length']
             @response.writeHead(imageResponse.statusCode, headers)
             stdout.pipe(@response)
         )


bubbles: legend for OD-colors

bubbles: legend for OD-colors

--- app/charts/bubbles.coffee
+++ app/charts/bubbles.coffee
@@ -160,7 +160,7 @@

   updateLegend = (selection, maxTotalMagnitude) ->
-    legendMargin = {top: 15, right: 2, bottom: 10, left: 2}
+    legendMargin = {top: 60, right: 2, bottom: 10, left: 2}

     maxR = rscale.range()[1]  # rscale(pow10(maxOrd))
     legend = selection.select("svg.legend")
@@ -182,6 +182,7 @@
       .attr("width", maxR*2 + legendMargin.left + legendMargin.right)
       .attr("height",maxR*2 + legendMargin.top + legendMargin.bottom)

+
     legend.select("g.outer")
       .attr("transform", "translate(#{legendMargin.left + maxR},#{legendMargin.top + maxR})")

@@ -338,10 +339,35 @@

     unless isUpdate
-      selection.append("svg")
-        .attr("class", "legend")
-      .append("g")
-        .attr("class", "outer")
+      do ->
+        legend = selection.append("svg")
+          .attr("class", "legend")
+
+        colors = legend.append("g")
+          .attr("class", "colors")
+
+        legend.append("g").attr("class", "outer")
+
+        itemEnter = colors.selectAll("g.item")
+          .data([{name:"Donated", color:"steelblue"}, {name:"Received", color:"#d56869"}])
+          .enter()
+        .append("g")
+          .attr("class", "item")
+          .attr("transform", (d,i) -> "translate(0, #{i*15})")
+
+        itemEnter.append("rect")
+          .attr("x", "28")
+          .attr("y", "0")
+          .attr("width", "15")
+          .attr("height", "10")
+          .attr("fill", (d) -> d.color)
+
+        itemEnter.append("text")
+          .attr("x", "47")
+          .attr("y", "5")
+          .attr("dominant-baseline", "middle")
+          .text((d) -> d.name)
+

     updateLegend(selection, maxTotalMagnitude)


Fix some help text

Fix some help text

--- app/classifier/index.cjsx
+++ app/classifier/index.cjsx
@@ -117,7 +117,7 @@
                     <TriggeredModalForm trigger={

                     }>
-
A "demo" classification won’t be counted during aggregation. Use this to give quick demos of your project without

+
A "throwaway" classification will be stored, but it won’t be counted during aggregation. Use this to give quick demos of your project without throwing off your data.

                     </TriggeredModalForm>


Run copy-i18n-tags (#5576)

Run copy-i18n-tags (#5576)


Update zh-HANS.coffee

Update zh-HANS.coffee

--- app/locale/zh-HANS.coffee
+++ app/locale/zh-HANS.coffee
@@ -1632,18 +1632,18 @@
 #    cs5_curriculum_desc_py: "Scope and sequence, lesson plans, activities and more for Course 5 classes using Python."
     cs1_pairprogramming: "组队编程活动"
     cs1_pairprogramming_desc: "让两个学生一起组队编程练习，这将有助于他们互相更好的学习与沟通。"
-#    gd1: "Game Development 1"
+    gd1: "游戏开发1"
 #    gd1_guide: "Game Development 1 - Project Guide"
 #    gd1_guide_desc: "Use this to guide your students as they create their first shareable game project in 5 days."
 #    gd1_rubric: "Game Development 1 - Project Rubric"
 #    gd1_rubric_desc: "Use this rubric to assess student projects at the end of Game Development 1."
-#    gd2: "Game Development 2"
+    gd2: "游戏开发2"
 #    gd2_curriculum: "Game Development 2 - Curriculum Guide"
 #    gd2_curriculum_desc: "Lesson plans for Game Development 2."
-#    gd3: "Game Development 3"
+    gd3: "游戏开发3"
 #    gd3_curriculum: "Game Development 3 - Curriculum Guide"
 #    gd3_curriculum_desc: "Lesson plans for Game Development 3."
-#    wd1: "Web Development 1"
+    wd1: "网页开发1"
 #    wd1_headlines: "Headlines & Headers Activity"
 #    wd1_headlines_example: "View sample solution"
 #    wd1_headlines_desc: "Why are paragraph and header tags important? Use this activity to show how well-chosen headers make web pages easier to read. There are many correct solutions to this!"
@@ -1651,7 +1651,7 @@
 #    wd1_html_syntax_desc: "One-page reference for the HTML style students will learn in Web Development 1."
 #    wd1_css_syntax: "CSS Syntax Guide"
 #    wd1_css_syntax_desc: "One-page reference for the CSS and Style syntax students will learn in Web Development 1."
-#    wd2: "Web Development 2"
+    wd2: "网页开发2"
 #    wd2_jquery_syntax: "jQuery Functions Syntax Guide"
 #    wd2_jquery_syntax_desc: "One-page reference for the jQuery functions students will learn in Web Development 2."
 #    wd2_quizlet_worksheet: "Quizlet Planning Worksheet"
@@ -1793,7 +1793,7 @@
     victory_course_share_web: "网页"
     victory_course_share_suffix: "分享给亲朋好友。"
     copy_url: "复制 URL"
-#    share_with_teacher_email: "Send to your teacher"
+    share_with_teacher_email: "发送给你的老师"

   game_dev:
     creator: "创建者"
@@ -2092,7 +2092,7 @@
     payments: "支付方式"
     prepaid_codes: "预付费码"
     purchased: "已购买"
-#    subscribe_for_gems: "Subscribe for gems"
+    subscribe_for_gems: "订阅以获得宝石"
     subscription: "订阅"
     invoices: "票据"
     service_apple: "设备：苹果"
@@ -2196,11 +2196,11 @@
     arguments: "参数"
     arithmetic: "四则运算"
     array_2d: "二维数组"
-#    array_index: "Array Indexing"
+    array_index: "数组下标"
 #    array_iterating: "Iterating Over Arrays"
 #    array_literals: "Array Literals"
 #    array_searching: "Array Searching"
-#    array_sorting: "Array Sorting"
+    array_sorting: "数组排序"
     arrays: "数组"
 #    basic_css_rules: "Basic CSS rules"
 #    basic_css_selectors: "Basic CSS selectors"
@@ -2219,7 +2219,7 @@
     break_statements: "Break语句"
     classes: "类"
     continue_statements: "Continue语句"
-#    dom_events: "DOM Events"
+    dom_events: "DOM事件"
 #    dynamic_styling: "Dynamic Styling"
 #    event_concurrency: "Event Concurrency"
 #    event_data: "Event Data"
@@ -2242,7 +2242,7 @@
     if_else_statements: "If/Else语句"
     if_statements: "If语句"
 #    if_statements_nested: "Nested If Statements"
-#    indexing: "Array Indexes"
+    indexing: "数组下标"
 #    input_handling_flags: "Input Handling - Flags"
 #    input_handling_keyboard: "Input Handling - Keyboard"
 #    input_handling_mouse: "Input Handling - Mouse"
@@ -2265,11 +2265,11 @@
 #    queues: "Data Structures - Queues"
 #    reading_docs: "Reading the Docs"
     recursion: "递归"
-#    return_statements: "Return Statements"
+    return_statements: "return语句"
 #    stacks: "Data Structures - Stacks"
     strings: "字符串"
 #    strings_concatenation: "String Concatenation"
-#    strings_substrings: "Substring"
+    strings_substrings: "子串"
 #    trees: "Data Structures - Trees"
     variables: "变量"
     vectors: "向量"
@@ -2302,15 +2302,15 @@
     basic_event_handling: "基本事件处理"
     create_sharable_interactive_web_page: "创建一个可分享的互动网页"

-#  anonymous_teacher:
+  anonymous_teacher:
 #    notify_teacher: "Notify Teacher"
-#    create_teacher_account: "Create free teacher account"
-#    enter_student_name: "Your name:"
-#    enter_teacher_email: "Your teacher's email:"
-#    teacher_email_placeholder: "teacher.email@example.com"
-#    student_name_placeholder: "type your name here"
-#    teachers_section: "Teachers:"
-#    students_section: "Students:"
+    create_teacher_account: "创建免费的老师账号"
+    enter_student_name: "你的名字:"
+    enter_teacher_email: "你的老师的电子邮箱:"
+    teacher_email_placeholder: "teacher.email@example.com"
+    student_name_placeholder: "输入你的名字"
+    teachers_section: "老师:"
+    students_section: "学生:"
 #    teacher_notified: "We've notified your teacher that you want to play more CodeCombat in your classroom!"

   delta:
@@ -2416,10 +2416,10 @@
     november: "十一月"
     december: "十二月"

-#  code_play_create_account_modal:
+  code_play_create_account_modal:
 #    title: "You did it!" # This section is only needed in US, UK, Mexico, India, and Germany
 #    body: "You are now on your way to becoming a master coder. Sign up to receive an extra 100 Gems & you will also be entered for a chance to win $2,500 & other Lenovo Prizes."
-#    sign_up: "Sign up & keep coding ▶"
+    sign_up: "注册 & 继续编程 ▶"
 #    victory_sign_up_poke: "Create a free account to save your code & be entered for a chance to win prizes!"
 #    victory_sign_up: "Sign up & be entered to win $2,500"

@@ -2447,14 +2447,14 @@
 #    missing_quotes: "Missing quotes. Try `$1`"
 #    argument_type: "`$1`'s argument `$2` should have type `$3`, but got `$4`: `$5`."
 #    argument_type2: "`$1`'s argument `$2` should have type `$3`, but got `$4`."
-#    target_a_unit: "Target a unit."
+    target_a_unit: "攻击一个目标。"
     attack_capitalization: "攻击 $1，而不是 $2。  (重要：请区分大小写。)"
 #    empty_while: "Empty while statement. Put 4 spaces in front of statements inside the while statement."
 #    line_of_site: "`$1`'s argument `$2` has a problem. Is there an enemy within your line-of-sight yet?"
     need_a_after_while: "在`$2`后面需要一个`$1`。"
 #    too_much_indentation: "Too much indentation at the beginning of this line."
 #    missing_hero: "Missing `$1` keyword; should be `$2`."
-#    takes_no_arguments: "`$1` takes no arguments."
+    takes_no_arguments: "`$1`无参数。"
 #    no_one_named: "There's no one named \"$1\" to target."
 #    separated_by_comma: "Function calls paramaters must be seperated by `,`s"
 #    protected_property: "Can't read protected property: $1"
@@ -2464,10 +2464,10 @@
 #    unexpected_end_of: "Unexpected end of input"
 #    unnecessary_semicolon: "Unnecessary semicolon."
 #    unexpected_token_expected: "Unexpected token: expected $1 but found $2 while parsing $3"
-#    unexpected_token: "Unexpected token $1"
-#    unexpected_token2: "Unexpected token"
-#    unexpected_number: "Unexpected number"
-#    unexpected: "Unexpected '$1'."
+    unexpected_token: "意外的标记$1"
+    unexpected_token2: "意外的标记"
+    unexpected_number: "意外的数字"
+    unexpected: "意外的 '$1'。"
 #    escape_pressed_code: "Escape pressed; code aborted."
 #    target_an_enemy: "Target an enemy by name, like `$1`, not the string `$2`."
 #    target_an_enemy_2: "Target an enemy by name, like $1."
@@ -2485,7 +2485,7 @@
 #    need_a_on_the: "Need a `:` on the end of the line following `$1`."
 #    attempt_to_call_undefined: "attempt to call '$1' (a nil value)"
 #    unterminated: "Unterminated `$1`"
-#    target_an_enemy_variable: "Target an $1 variable, not the string $2. (Try using $3.)"
+    target_an_enemy_variable: "以变量$1作为目标, 而不是字符串$2。 (试着用$3。)"
 #    error_use_the_variable: "Use the variable name like `$1` instead of a string like `$2`"
 #    indentation_unindent_does_not: "Indentation unindent does not match any outer indentation level"
 #    unclosed_paren_in_function_arguments: "Unclosed $1 in function arguments."
@@ -2495,7 +2495,7 @@
     there_is_no_function: "没有`$1`函数，但是`$2`有`$3`函数。"
 #    attacks_argument_enemy_has: "`$1`'s argument `$2` has a problem."
 #    is_there_an_enemy: "Is there an enemy within your line-of-sight yet?"
-    target_is_null_is: "目标$1出现了问题（可能是null）。这里总是有攻击目标吗？"
+    target_is_null_is: "目标$1出现了问题（可能是null）。这里总是有攻击目标吗？(试着用$2。)"
     hero_has_no_method: "`$1` 没有 `$2` 函数。"
     there_is_a_problem: "你的代码有问题。"
 #    did_you_mean: "Did you mean $1? You do not have an item equipped with that skill."


started pulling vimeo videos

started pulling vimeo videos

--- server.coffee
+++ server.coffee
@@ -12,9 +12,14 @@
   app.set('views',__dirname + "/views/")

 # server routes
+vimeo = require('./lib/vimeo')
+vimeo.keyword = 'LNUG'

 app.get "/", (req,res) ->
-  res.render 'lnug.html', layout: false
+  videos = vimeo.request('forwardtechnology')
+  console.log vimeo.keyword
+  res.render 'lnug.html', layout: false, locals:
+                                            videos = vimeo.videos

 port = process.env.PORT || 8080
 app.listen port


healthchecks

healthchecks

--- server.coffee
+++ server.coffee
@@ -63,6 +63,12 @@

 app.use router

+router.get '/ping', (req, res) ->
+  res.end 'pong'
+
+router.get '/healthcheck', (req, res) ->
+  res.json {healthy: true}
+
 # Routes
 router.get '*', (req, res) ->
   renderHomePage()


consistency

consistency

--- server.coffee
+++ server.coffee
@@ -59,10 +59,10 @@
         'port'
         'ip'

+        'death_timeout'
+
         'source_maps'
         'livereload'
-
-        'death_timeout'
       ]
       _.omit(_.clone(config), excludedKeys...)


Detatch messages pane between runs

Detatch messages pane between runs

--- lib/atom-tinyrave.coffee
+++ lib/atom-tinyrave.coffee
@@ -61,6 +61,7 @@

     # Purge any error messages from last session
     if @messages
+      @messages.detach()
       @messages.clear()

     view = @atomTinyraveView.getSandbox()


Fix template helper deprecation warnings

--- sample.coffee
+++ sample.coffee
@@ -133,8 +133,9 @@
       userId: () ->
          Meteor.userId()

-   Template.nav.active = (pill) ->
-      return "active" if pill is "#{this}"
+   Template.nav.helpers
+      active: (pill) ->
+         return "active" if pill is "#{this}"

    fileTableHelpers =
       owner: () ->
@@ -175,16 +176,17 @@

    Template.gallery.helpers fileTableHelpers

-   Template.gallery.dataEntries = () ->
-      # Reactively populate the table
-      this.find({'metadata.thumbOf': {$exists: false}}, {sort:{filename: 1}})
-
-   Template.gallery.thumb = () ->
-      "#{this.metadata.thumb}"
-
-   Template.gallery.rendered = () ->
-      # This assigns a file drop zone to the "file table"
-      this.data.resumable.assignDrop $(".#{myData.root}DropZone")
+   Template.gallery.helpers
+      dataEntries: () ->
+         # Reactively populate the table
+         this.find({'metadata.thumbOf': {$exists: false}}, {sort:{filename: 1}})
+
+      thumb: () ->
+         "#{this.metadata.thumb}"
+
+      rendered: () ->
+         # This assigns a file drop zone to the "file table"
+         this.data.resumable.assignDrop $(".#{myData.root}DropZone")

    Template.fileControls.events
       'click .remove-files': (e, t) ->
@@ -192,9 +194,11 @@
          this.find({}).forEach ((d) -> this.remove(d._id)), this

    Template.fileTable.helpers fileTableHelpers
-   Template.fileTable.dataEntries = () ->
-      # Reactively populate the table
-      this.find({}, {sort:{filename: 1}})
+
+   Template.fileTable.helpers
+      dataEntries: () ->
+         # Reactively populate the table
+         this.find({}, {sort:{filename: 1}})

    Template.fileTable.events fileTableEvents


change recoverhandlerhelper require path

--- servers/lib/server/handlers/recover.test.coffee
+++ servers/lib/server/handlers/recover.test.coffee
@@ -9,10 +9,8 @@
   generateRandomString
   generateRandomUsername

-  RecoverHandlerHelper
   RegisterHandlerHelper }                 = require '../../../testhelper'
-
-{ generateRecoverRequestParams }          = RecoverHandlerHelper
+{ generateRecoverRequestParams }          = require '../../../testhelper/recoverhandlerhelper'
 { generateRegisterRequestParams }         = RegisterHandlerHelper


Add Bootstrap 2.2.0 and Knockout.js

Add Bootstrap 2.2.0 and Knockout.js

--- servers/www/assets/js/services/catalogue.coffee
+++ servers/www/assets/js/services/catalogue.coffee
@@ -192,6 +192,10 @@
           "dependencies":
             "bootstrap-css-combined": "2.1.1"
             "bootstrap-js": "2.1.1"
+        "2.2.0":
+          "dependencies":
+            "bootstrap-css-combined": "2.2.0"
+            "bootstrap-js": "2.2.0"
     "bootstrap-css-combined":
       "category": "Bootstrap"
       "tags": ["bootstrap"]
@@ -206,6 +210,10 @@
           "transform": [
             ["head", "append", "<link href=\"//netdna.bootstrapcdn.com/twitter-bootstrap/2.1.1/css/bootstrap-combined.min.css\" rel=\"stylesheet\">"]
           ]
+        "2.2.0":
+          "transform": [
+            ["head", "append", "<link href=\"//netdna.bootstrapcdn.com/twitter-bootstrap/2.2.0/css/bootstrap-combined.min.css\" rel=\"stylesheet\">"]
+          ]
     "bootstrap-js":
       "category": "Bootstrap"
       "keywords": ["bootstrap"]
@@ -219,6 +227,12 @@
           "transform": [
             ["head", "append", "<script src=\"//netdna.bootstrapcdn.com/twitter-bootstrap/2.1.1/js/bootstrap.min.js\"></script>"]
           ]
+        "2.2.0":
+          "dependencies":
+            "jquery": ">= 1.7.0"
+          "transform": [
+            ["head", "append", "<script src=\"//netdna.bootstrapcdn.com/twitter-bootstrap/2.2.0/js/bootstrap.min.js\"></script>"]
+          ]
     "bootstrap-starter":
       "category": "Bootstrap"
       "tags": ["bootstrap"]
@@ -281,6 +295,23 @@
             "backbone": "~1.4.0"
           "transform": [
             ["head", "append", "<script src=\"//cdnjs.cloudflare.com/ajax/libs/backbone-localstorage.js/1.0/backbone.localStorage-min.js\"></script>"]
+          ]
+    "knockout":
+      "category": "Knockout.js"
+      "tags": "knockout"
+      "versions":
+        "2.1.0":
+          "transform": [
+            ["head", "append", "<script src=\"//cdnjs.cloudflare.com/ajax/libs/knockout/2.1.0/knockout-min.js\"></script>"]
+          ]
+    "knockout.mapping":
+      "category": "Knockout.js"
+      "versions":
+        "2.3.2":
+          "dependencies":
+            "knockout": "2.1.x"
+          "transform": [
+            ["head", "append", "<script src=\"//cdnjs.cloudflare.com/ajax/libs/knockout.mapping/2.3.2/knockout.mapping.js\"></script>"]
           ]

   new class Catalogue


Put explicit curlies in nested object literal

--- workers/social/lib/social/models/user/index.coffee
+++ workers/social/lib/social/models/user/index.coffee
@@ -1634,7 +1634,7 @@

         account.sendNotification 'SessionHasEnded', { clientId }

-        selector = { clientId: $ne: client.sessionToken }
+        selector = { clientId: { $ne: client.sessionToken } }

         user.killSessions selector, callback


Make Player's physics body smaler to make the movement more plausable

--- app/models/Player.coffee
+++ app/models/Player.coffee
@@ -27,8 +27,9 @@
       type: 'dynamic'
       x: @position.x
       y: @position.y
-      halfWidth: @size.x / 2
-      halfHeight: @size.y / 2
+      # make the player's physBody smaller to make movements more plausable
+      halfWidth: @size.x / 4
+      halfHeight: @size.y / 4
       damping: 0
       angle: 0
       categories: ['']
@@ -86,7 +87,7 @@

     # translate to drawing center
     dx = dx - Math.floor(sw/2)
-    dy = dy - Math.floor(sh/2)
+    dy = dy - Math.floor((sh/4)*3)

     ctx.drawImage img, sx, sy, sw, sh, dx, dy, dw, dh


rename phantomjs

rename phantomjs

--- module/chart_image.coffee
+++ module/chart_image.coffee
@@ -1,6 +1,6 @@
 path         = require('path')
 childProcess = require('child_process')
-phantomjs    = require('phantomjs')
+phantomjs    = require('phantomjs-prebuilt')
 crypto       = require("crypto")

 class ChartImage


get a debugging deploys post hook out there

--- nodeko.coffee
+++ nodeko.coffee
@@ -373,6 +373,10 @@
       person.save (error, resp) =>
         @redirectToTeam person

+post '/deploys', ->
+  sys.log sys.inspect(@req)
+  @next()
+
 get '/prizes', ->
   @render 'prizes.html.jade', { layout: 'layout.haml' }


Fix for prevArrow and nextArrow

Fix for prevArrow and nextArrow

--- app/scripts/app.coffee
+++ app/scripts/app.coffee
@@ -105,8 +105,8 @@
             useCSS: scope.useCSS isnt "false"
             variableWidth: scope.variableWidth is "true"
             vertical: scope.vertical is "true"
-            prevArrow: if scope.prevArrow then $(scope.prevArrow) else undefined
-            nextArrow: if scope.nextArrow then $(scope.nextArrow) else undefined
+            prevArrow: if scope.prevArrow then scope.prevArrow else undefined
+            nextArrow: if scope.nextArrow then scope.nextArrow else undefined

           slider.on 'init', (sl) ->


disqus.coffee was moved to boot.coffee

disqus.coffee was moved to boot.coffee

--- app/scripts/disqus.coffee
+++ app/scripts/disqus.coffee
@@ -1,16 +0,0 @@
-$ ->
-  window.disqus_url = "http://cmx.io/"
-  window.disqus_url += "gist/#{window.gistId}" if window.gistId
-
-  wrapper = $("<div/>").attr('id', "disqus_thread")
-  if window.gistId
-    $('#comix').append(wrapper)
-    window.disqus_title = "Comix ##{window.gistId}"
-  else
-    $('.discussion').prepend(wrapper)
-
-  dsq = document.createElement("script")
-  dsq.type = "text/javascript"
-  dsq.async = true
-  dsq.src = "http://cmxio.disqus.com/embed.js"
-  (document.getElementsByTagName("head")[0] or document.getElementsByTagName("body")[0]).appendChild dsq


hook append image

hook append image

--- app/scripts/editor.coffee
+++ app/scripts/editor.coffee
@@ -249,6 +249,8 @@
 		$('#tooltip').delegate '.left-panel li:nth-child(3)', 'click', (e)=>
 			@toggleFormatBlock $('.hovered'), 'pre'
 			@updateTooltipStatus $('.hovered')
+		$('#tooltip').delegate '.right-panel li:nth-child(1)', 'click', (e)=>
+			console.log 'append image'
 		$('body').delegate '#tooltip', 'mouseleave', (e)=>
 			@hideTooltip()
 		$('body').delegate '.content', 'mouseleave', (e)=>


Change destroy to delete

Change destroy to delete

Closes #1251

--- app/talk/moderations.cjsx
+++ app/talk/moderations.cjsx
@@ -95,7 +95,7 @@
             .map (action) =>
               if action is 'destroy'
                 <button key={action} className="moderations-#{action}" onClick={=>
-                  if window.confirm("Are you sure that you want to destroy the reported comment?")
+                  if window.confirm("Are you sure that you want to delete the reported comment?")
                     @updateModeration(moderation, action)
                 }>{action}</button>
               else


Disable diffing for large files

Disable diffing for large files

--- lib/git-diff-view.coffee
+++ lib/git-diff-view.coffee
@@ -1,5 +1,7 @@
 {CompositeDisposable} = require 'atom'
 {repositoryForPath} = require './helpers'
+
+MAX_BUFFER_LENGTH_TO_DIFF = 2 * 1024 * 1024

 module.exports =
 class GitDiffView
@@ -101,8 +103,13 @@

     @removeDecorations()
     if path = @editor?.getPath()
-      if @diffs = @repository?.getLineDiffs(path, @editor.getText())
-        @addDecorations(@diffs)
+
+      # TODO - remove this fallback to a private API once Atom 1.25 ships.
+      length = @editor.getBuffer().getLength?() ? @editor.getBuffer().buffer.getLength()
+
+      if length < MAX_BUFFER_LENGTH_TO_DIFF
+        @diffs = @repository?.getLineDiffs(path, @editor.getText())
+        @addDecorations(@diffs) if @diffs

   addDecorations: (diffs) ->
     for {newStart, oldLines, newLines} in diffs


Replace substr with substring

Replace substr with substring

--- modules/player/hud.coffee
+++ modules/player/hud.coffee
@@ -236,7 +236,7 @@
     for qI, i in q.items.slice offset, max
       r.description += """
       **#{offset+i+1}.** \
-      [#{qI.title.replace(/\]/, '\\]').substr(0, 50)}]\
+      [#{qI.title.replace(/\]/, '\\]').substring(0, 50)}]\
       (#{qI.sauce.replace(/\)/, '\\)')}) \
       #{@util.displayFilters qI.filters} \
       (#{@util.displayTime qI.duration}) \


Only provide default args for template in show command

--- bin/layer-show.coffee
+++ bin/layer-show.coffee
@@ -9,6 +9,8 @@
   .describe 'template-data', 'The jade template to show'
   .describe 'template-jade', 'The saved template data to show'

+args.default arg, undefined for arg in ['template-jade', 'template-data']
+
 args = args.camelize()

 templates.show args.template, args.templateJade, args.templateData


Forgot to require fs.

Forgot to require fs.

--- server.coffee
+++ server.coffee
@@ -1,3 +1,4 @@
+fs = require 'fs'
 path = require 'path'
 https = require 'https'
 express = require 'express'


hotfix for register redirect if there is no demo user

--- server.coffee
+++ server.coffee
@@ -249,9 +249,12 @@
             for deck in demoDecks
               delete deck._id
               deck.username = req.body.username
-            db.collection('decks').insert demoDecks, (err, newDecks) ->
-              throw err if err
-              res.json(200, {user: req.user, decks: newDecks})
+            if demoDecks.length > 0
+              db.collection('decks').insert demoDecks, (err, newDecks) ->
+                throw err if err
+                res.json(200, {user: req.user, decks: newDecks})
+            else
+              res.json(200, {user: req.user, decks: []})

 app.get '/check/:username', (req, res) ->
   db.collection('users').findOne username: req.params.username, (err, user) ->


remote comments

remote comments

--- server.coffee
+++ server.coffee
@@ -119,15 +119,7 @@
               author: commits[messagesCount - i - 1].author
             callback undefined, message
     async.parallel workers, (errors, messages) ->
-      res.render "thread", messages: messages
-  # communityRepo.contents "1.md", "links-to-animations-examples", (error, blob) ->
-  #   console.log "xxx", error, blob
-  #   content = new Buffer(blob.content, "base64").toString("utf8")
-  #   res.render "thread", messages: [{content: content}]
-  # communityRepo = github.repo url
-  # communityRepo.branches (error, branches) ->
-  #   console.log branches
-  #   res.render "community", threads: branches, communityUrl: url
+      res.render "thread", messages: messages

 port = process.env.PORT || 8090
 app.listen port


it helps to know what path each harp server is running, since there can now be several of them

--- server.coffee
+++ server.coffee
@@ -6,7 +6,7 @@

 harps = (path, port, mode = "server") ->
   harp[mode] path, port: port, ->
-    console.log " Harp v#{pkgv} on http://localhost:#{port}"
+    console.log " Harp v#{pkgv} as http://localhost:#{port} serving #{path}"

 # start at least one server, or multihost some path (multihost is the default mode)
 harps(o.harp.path, o.harp.port, o.harp.mode)


for got to move require crypto

for got to move require crypto

--- server.coffee
+++ server.coffee
@@ -2,7 +2,6 @@
 config = require './config.json'
 routes = require('./routes').open("./tiles", config)
 fs = require 'fs'
-crypto = require 'crypto'
 cache = require("./cache").open(config)
 preview = fs.readFileSync './preview.html', 'utf8'
 kublai = express()


Neutralize/Capture part

Neutralize/Capture part

--- server.coffee
+++ server.coffee
@@ -28,7 +28,9 @@
 	log 'Adding marker: lat=', location.lat, ', lng=', location.lng
 	Db.shared.set 'game', 'beacons', location.lat.toString()+'_'+location.lng.toString(), {location: location}
 	Db.shared.set 'game', 'beacons', location.lat.toString()+'_'+location.lng.toString(), 'owner', -1
+	Db.shared.set 'game', 'beacons', location.lat.toString()+'_'+location.lng.toString(), 'nextOwner', -1
 	Db.shared.set 'game', 'beacons', location.lat.toString()+'_'+location.lng.toString(), 'captureValue', 10
+

 # Set the round time unit and number
 exports.client_setRoundTime = (roundTimeNumber, roundTimeUnit) ->
@@ -82,6 +84,8 @@
 			#log 'beaconLoop: beacon=', beacon, ', beaconRadius=', beaconRadius, ', distance=', beaconDistance, ', current=', current, ', new=', newStatus
 			if newStatus != current
 				if newStatus
+					owner = beacon.get 'owner'
+
 					# TODO: move lower, after updating other data
 					log 'Adding to inRange: id=', client, ', name=', Plugin.userName(client)
 					beacon.set 'inRange', client, 'true'
@@ -99,7 +103,7 @@
 						team = getTeamOfUser(player.n)
 						teamMembers[team] = teamMembers[team]+1
 					# TODO: remove
-					teamMembers = [0, 2, 1, 0, 3, 3]
+					#teamMembers = [0, 2, 1, 0, 3, 3]

 					log 'teamMembers count: ', teamMembers

@@ -116,10 +120,16 @@
 					# Check if there should be progress
 					if competing.length == 1
 						# Team will capture the flag
-						log 'Team ', competing[0], ' is capturing beacon ', beacon.n
-
-
-
+						activeTeam = competing[0]
+						beacon.set 'nextOwner', activeTeam
+						if owner == -1
+							# Capturing
+							log 'Team ', activeTeam, ' is capturing beacon ', beacon.n
+
+
+						else
+							# Neutralizing
+							log 'Team ', activeTeam, ' is neutralizing beacon ', beacon.n

 					else if competing.length > 1
 						# No progess, stand-off


server: static maxAge 4h, fixes #23

server: static maxAge 4h, fixes #23

--- server.coffee
+++ server.coffee
@@ -72,8 +72,8 @@
   res.json {name: 'Zorium'}

 if config.ENV is config.ENVS.PROD
-then app.use express['static'](gulpConfig.paths.dist)
-else app.use express['static'](gulpConfig.paths.build)
+then app.use express.static(gulpConfig.paths.dist, {maxAge: '4h'})
+else app.use express.static(gulpConfig.paths.build, {maxAge: '4h'})

 app.use router
 app.use (req, res, next) ->


Statics assess first.

Statics assess first.

Because * catches also assets.

--- app/server/js/app.coffee
+++ app/server/js/app.coffee
@@ -14,7 +14,6 @@
     app = express()

     middleware.use app
-    router.use app

     if isDev
       # Square brackets syntax until we will have Express 4 externs.
@@ -25,5 +24,7 @@
       # Compiled script has per deploy specific url so set maxAge to one year.
       app['use'] '/app', express['static'] 'app', 'maxAge': 31557600000

+    router.use app
+
     app['listen'] port, ->
       console.log 'Express server listening on port ' + port


Hide nsfw media from panel accordingly

Hide nsfw media from panel accordingly

--- resources/assets/coffee/react/_components/beatmapset-panel.coffee
+++ resources/assets/coffee/react/_components/beatmapset-panel.coffee
@@ -19,6 +19,8 @@
   render: =>
     # this is actually "beatmapset"
     beatmapset = @props.beatmap
+
+    @showVisual = !beatmapset.nsfw || (currentUser?.user_preferences?.beatmapset_show_nsfw ? false)

     showHypeCounts = beatmapset.hype?
     if showHypeCounts
@@ -68,16 +70,17 @@
               el BeatmapIcon, beatmap: b

     div
-      className: 'beatmapset-panel js-audio--player'
+      className: "beatmapset-panel #{if @showVisual then 'js-audio--player' else ''}"
       'data-audio-url': beatmapset.preview_url
       div className: 'beatmapset-panel__panel',
         a
           href: laroute.route('beatmapsets.show', beatmapset: beatmapset.id)
           className: 'beatmapset-panel__header',
-          el Img2x,
-            className: 'beatmapset-panel__image'
-            onError: @hideImage
-            src: beatmapset.covers.card
+          if @showVisual
+            el Img2x,
+              className: 'beatmapset-panel__image'
+              onError: @hideImage
+              src: beatmapset.covers.card
           div className: 'beatmapset-panel__image-overlay'
           div className: 'beatmapset-panel__status-container',
             if beatmapset.video
@@ -142,9 +145,10 @@
               @renderDownloadLink()

           div className: 'beatmapset-panel__difficulties', difficulties
-      button
-        type: 'button'
-        className: 'beatmapset-panel__play js-audio--play'
+      if @showVisual
+        button
+          type: 'button'
+          className: 'beatmapset-panel__play js-audio--play'
       div className: 'beatmapset-panel__shadow'


fix rclient check in migration metrics

fix rclient check in migration metrics

--- services/document-updater/app/coffee/RedisMigrationManager.coffee
+++ services/document-updater/app/coffee/RedisMigrationManager.coffee
@@ -106,7 +106,7 @@
 			@migrationClient.findQueue @queueKey, (err, rclient) =>
 				return releaseLock(err) if err?
 				# add metric for updates
-				dest = (if rclient == @rclient_new then "new" else "old")
+				dest = (if rclient == @migrationClient.rclient_new then "new" else "old")
 				metrics.count "migration", @updates_count, 1, {status: "#{@migrationClient.migration_phase}-#{dest}"}
 				multi = rclient.multi()
 				for entry in @command_list


Remove deprecated fs.existsSync

Remove deprecated fs.existsSync

--- lib/linter-pylama.coffee
+++ lib/linter-pylama.coffee
@@ -1,7 +1,7 @@
-fs = require "fs"
 path = require 'path'
 helpers = require 'atom-linter'
 {CompositeDisposable} = require 'atom'
+{statSync} = require "fs"

 regex =
@@ -14,8 +14,6 @@

 class LinterPylama
-  @pylamaPath: ''
-
   constructor: ->
     @subscriptions = new CompositeDisposable
     @subscriptions.add atom.config.observe 'linter-pylama.pylamaVersion',
@@ -58,7 +56,7 @@

     @subscriptions.add atom.config.observe 'linter-pylama.usePylint',
     (usePyLint) =>
-      @usePyLint_ = usePyLint
+      @usePyLint = usePyLint

     @subscriptions.add atom.config.observe 'linter-pylama.lintOnFly',
     (lintOnFly) =>
@@ -97,30 +95,32 @@

   initPylama: =>
-    pylamaPath = @executablePath
-    if @pylamaVersion is 'external' and pylamaPath isnt @pylamaPath
-      if /^(pylama|pylama\.exe)$/.test pylamaPath
+    if @pylamaVersion is 'external' and @executablePath isnt @pylamaPath
+      @pylamaPath = ''
+      if /^(pylama|pylama\.exe)$/.test @executablePath
         processPath = process.env.PATH or process.env.Path
-        processPath.split(path.delimiter).forEach (dir) =>
-          tmp = path.join dir, pylamaPath
-          if fs.existsSync tmp
-            pylamaPath = tmp
-
-      if not path.isAbsolute pylamaPath
-        pylamaPath = path.resolve pylamaPath
-
-      if not fs.existsSync pylamaPath or not do fs.statSync(pylamaPath).isFile
+        for dir in processPath.split path.delimiter
+          tmp = path.join dir, @executablePath
+          try
+            @pylamaPath = tmp if do statSync(tmp).isFile
+            break
+          catch e
+      else
+        if @executablePath
+          tmp = if not path.isAbsolute @executablePath then path.resolve @executablePath else @executablePath
+          try
+            @pylamaPath = tmp if do statSync(tmp).isFile
+          catch e
+
+      if not @pylamaPath
         atom.notifications.addError 'Pylama executable not found',
-        detail: "[linter-pylama] `#{pylamaPath}` executable file not found. \
-        Please set the correct path to `pylama`."
-        @pylamaPath = ''
-      else
-        @pylamaPath = pylamaPath
+        detail: "[linter-pylama] `#{@executablePath}` executable file not found.
+        \nPlease set the correct path to `pylama`."
     else
       if process.platform is 'win32'
         @pylamaPath = path.join path.dirname(__dirname), 'bin', 'pylama.bat'
       else
-        @pylamaPath = path.join path.dirname(__dirname), 'bin', 'pylama.py'
+        @pylamaPath = path.join path.dirname(__dirname), 'bin', 'pylama.py',

   initArgs: (curDir) =>
@@ -134,7 +134,7 @@
       if @ignoreErrorsAndWarnings then args.push.apply args, ['--ignore', @ignoreErrorsAndWarnings]
       if @skipFiles then args.push.apply args, ['--skip', @skipFiles]

-      usePyLint = if @usePyLint_ then 'pylint' else ''
+      usePyLint = if @usePyLint then 'pylint' else ''
       useMcCabe = if @useMcCabe then 'mccabe' else ''
       usePEP8 = if @usePEP8 then 'pep8' else ''
       usePEP257 = if @usePEP257 then 'pep257' else ''


removed exclamation mark

removed exclamation mark

--- src/core/javascripts/controllers/event_list.js.coffee
+++ src/core/javascripts/controllers/event_list.js.coffee
@@ -356,7 +356,7 @@
             for i in item.chain.extra[name]
               filter = ($scope.dynamic_filters.values[dynamic_filter.name] and i is $scope.dynamic_filters.values[dynamic_filter.name].name) or !$scope.dynamic_filters.values[dynamic_filter.name]?
               break if filter
-          else if (item.chain.extra[name] is undefined && !item.chain?)
+          else if (item.chain.extra[name] is undefined && item.chain?)
             filter = true;
           result = result and filter
       else


Check chain is set

Check chain is set

--- src/core/javascripts/controllers/event_list.js.coffee
+++ src/core/javascripts/controllers/event_list.js.coffee
@@ -342,7 +342,7 @@
         for dynamic_filter in $scope.dynamic_filters['check']
           name = dynamic_filter.name.parameterise('_')
           filter = false
-          if item.chain.extra[name]
+          if item.chain and item.chain.extra[name]
             for i in item.chain.extra[name]
               filter = ($scope.dynamic_filters.values[dynamic_filter.name] and i is $scope.dynamic_filters.values[dynamic_filter.name].name) or !$scope.dynamic_filters.values[dynamic_filter.name]?
               break if filter


Fix submodule logic and small cleanup

Fix submodule logic and small cleanup

--- client/modules/pkgman.coffee
+++ client/modules/pkgman.coffee
@@ -24,7 +24,7 @@

 		if impl?

-			path = "#{submodule}/#{@_path}"
+			path = "#{@_path}/#{submodule}"

 		else

@@ -54,10 +54,8 @@
 			continue if error.toString() is "Error: Cannot find module '#{name}'"
 			throw error

-		if module_?
-
-			modules[name] = module_
-			debug "Found package #{name}."
+		modules[name] = module_
+		debug "Found package #{name}."

 	# Collect hooks.
 	for path, module_ of modules


default_ids may be null

default_ids may be null

--- client/skr/models/Sku.coffee
+++ client/skr/models/Sku.coffee
@@ -5,7 +5,7 @@
         id:                  {type:"integer"}
         default_vendor_id:   {type:"integer"}
         gl_asset_account_id: {type:"integer", default: ->
-            Skr.Models.GlAccount.default_ids.asset
+            Skr.Models.GlAccount.default_ids?.asset
         }
         default_uom_code:    {type:"string"}
         code:                {type:"code",   required:true}


removed useless comment

removed useless comment

--- client/src/app/puppet.coffee
+++ client/src/app/puppet.coffee
@@ -218,9 +218,6 @@
         assetNamesToData[name] = image
       else if splitUrl.mimeType.indexOf("audio/") == 0
         audio = new Audio(dataUrl)
-        # source = RW.audioContext.createMediaElementSource(audio)
-        # source.disconnect()
-        # source.connect(RW.lineOut.destination)
         assetNamesToData[name] = audio
       else
         assetNamesToData[name] = atob(splitUrl.data)


WatchSidebar: Use kernel display names

WatchSidebar: Use kernel display names

--- lib/watch-sidebar.coffee
+++ lib/watch-sidebar.coffee
@@ -8,9 +8,6 @@
 module.exports =
 class WatchSidebar
     constructor: (@kernel) ->
-        KernelManager = require './kernel-manager'
-        @language = KernelManager.getGrammarLanguageFor @kernel.grammar
-
         @element = document.createElement('div')
         @element.classList.add('hydrogen', 'watch-sidebar')

@@ -19,7 +16,7 @@

         languageDisplay = document.createElement('button')
         languageDisplay.classList.add('btn', 'icon', 'icon-sync')
-        languageDisplay.innerText = "Watch: #{@language}"
+        languageDisplay.innerText = "Watch: #{@kernel.kernelSpec.display_name}"
         languageDisplay.onclick = ->
             editor = atom.workspace.getActiveTextEditor()
             editorView = atom.views.getView(editor)


Updated default coffee task

Updated default coffee task

--- src/codo.coffee
+++ src/codo.coffee
@@ -1,3 +1,15 @@
 'use strict'

 module.exports = (grunt) ->
+
+  codo = require 'codo'
+
+  grunt.registerTask 'codo', 'Generates Codo documentation', ->
+
+    options = @options
+      source: 'src'
+      output: 'doc'
+
+    done = @async()
+
+    done()


Sidebar: small refactoring in the machine item

--- client/Main/navigation/navigationmachineitem.coffee
+++ client/Main/navigation/navigationmachineitem.coffee
@@ -32,23 +32,26 @@

     computeController.on "public-#{@machine._id}", (event)=>

-      if event.percentage?
+      {percentage, status} = event

-        if @progress.bar?
+      if percentage?
+
+        if @progress.bar

           @progress.show()
-          @progress.updateBar event.percentage
-          if event.percentage is 100
+          @progress.updateBar percentage
+
+          if percentage is 100
             KD.utils.wait 1000, @progress.bound 'hide'

       else

         @progress.hide()

-      if event.status?
+      if status?

         @unsetClass stateClasses
-        @setClass event.status.toLowerCase()
+        @setClass status.toLowerCase()

   click: (event)->
@@ -60,6 +63,6 @@
   pistachio:->

     """
-

#{@alias}
-      {{> @progress}}
+

#{@alias}
+    {{> @progress}}
     """


payment: reorder predicates to remove an unnecessary server roundtrip

--- client/app/MainApp/payment/paymentcontroller.coffee
+++ client/app/MainApp/payment/paymentcontroller.coffee
@@ -68,19 +68,16 @@
         JPaymentSubscription.fetchUserSubscriptions (err, subs) ->
           findActiveSubscription subs, planCode, callback

-  confirmPayment: ->
-    console.error 'confirm payment is removed'
-
   makePayment: (paymentMethodId, plan, amount) ->
     vmController = KD.getSingleton('vmController')

-    if 'string' is typeof plan
+    if amount is 0
+      vmController.createGroupVM type, plan.code or plan
+
+    else if 'string' is typeof plan
       @fetchPlanByCode plan, (err, planObj) =>
-        return console.error err  if err
+        return  if KD.showError err
         @makePayment paymentMethodId, planObj, amount
-
-    else if amount is 0
-      vmController.createGroupVM type, plan.code

     # else if type in ['group', 'expensed']
     #   paymentMethod = { plan: plan.code, multiple: yes }


Separated adding new introduction group to 2 form.

--- client/app/MainApp/sidebar/modals/adminmodal.coffee
+++ client/app/MainApp/sidebar/modals/adminmodal.coffee
@@ -411,7 +411,7 @@

     @buttonsContainer.addSubView @addButton = new KDButtonView
       cssClass : "editor-button"
-      title    : "Add New"
+      title    : "Add New Introduction Group"
       callback : => @showForm()

     @container = new KDView
@@ -462,12 +462,13 @@
           delegate: @
         ,snippet

-  showForm: (type = "Group", data = @getData(), actionType = "Insert") ->
+  showForm: (type = "Group", data = @getData(), actionType = "Insert", addingToAGroup = no) ->
     @buttonsContainer.hide()
     @container.hide()
     @addSubView @form = new IntroductionAdminForm {
       type
       actionType
+      addingToAGroup
       delegate: @
     }
     , data
@@ -637,7 +638,6 @@
             itemClass : KDView
             partial   : "Date format should be YYYY-MM-DD. e.g. 2013-05-03"
             cssClass  : "help-text info"
-
       Overlay         :
         label         : "Overlay"
         type          : "select"
@@ -653,7 +653,6 @@
             cssClass  : "help-text warning"
         change: =>
           @setVisibilityOfOverlayWarning @inputs.Overlay.getValue() is "no" # wtf "no"
-
       Visibility       :
         label         : "Visibility"
         type          : "select"
@@ -686,11 +685,7 @@
             cssClass  : "introduction-snippet-switch snippet"
             click     : => @switchMode()

-    options.fields  = itemFields
-    if options.type is "Group"
-      if options.actionType is "Insert"
-        groupFields[key] = field for key, field of itemFields
-      options.fields = groupFields
+    options.fields = if options.type is "Group" then groupFields else itemFields

     options.buttons =
       "Save"          :
@@ -713,15 +708,15 @@

     super options, data

-    {type, actionType} = @getOptions()
-    @utils.defer => @setAceEditor() if (type is "Group" and actionType is "Insert") or type is "Item"
-
-    @setVisibilityOfOverlayWarning @parentData.overlay is "no" # wtf "no"
+    @utils.defer => @setAceEditor() if @getOptions().type is "Item"
+    if @getOptions().type is "Group"
+      @setVisibilityOfOverlayWarning @parentData.overlay is "no" # wtf "no"

   save: ->
-    {actionType} = @getOptions()
-
-    if @getOptions().type is "Group"
+    options      = @getOptions()
+    {actionType, addingToAGroup} = options
+
+    if @getOptions().type is "Group" or addingToAGroup
       return @insertParent() if actionType is "Insert"
       @updateParent()
     else
@@ -729,9 +724,15 @@
       @updateChild()

   insertParent: ->
-    KD.remote.api.JIntroSnippet.create @createPostData(), =>
-      @buttons["Save"].hideLoader()
-      @emit "IntroductionFormNeedsReload"
+    data = @createPostData()
+    if not @getOptions().addingToAGroup
+      @destroy()
+      @getDelegate().showForm "Item", data, "Insert", yes
+    else
+      @parentData.snippets.push data
+      KD.remote.api.JIntroSnippet.create @parentData, =>
+        @buttons["Save"].hideLoader()
+        @emit "IntroductionFormNeedsReload"

   insertChild: ->
     @parentData.addChild @createPostData(), =>
@@ -750,33 +751,26 @@

   createPostData: ->
     {inputs}     = @
-    options      = @getOptions()
-    {type}       = options
-    {actionType} = options
-    itemData     = {}
-
-    snippet = @aceEditor?.getValue()
-    if @snippetType is "Text"
-      snippet = """new KDView({partial: "#{@aceEditor?.getValue()}"});"""
-
-    if type is "Item" or (type is "Group" and actionType is "Insert")
-      itemData = {
-        introId    : inputs.IntroId.getValue()
-        introTitle : inputs.IntroTitle.getValue()
-        snippet
-      }
-
-    if type is "Group"
-      data = {
+
+    if @getOptions().type is "Group"
+      groupData =
         title      : inputs.Title.getValue()
         expiryDate : inputs.ExpiryDate.getValue()
         visibility : inputs.Visibility.getValue()
         overlay    : inputs.Overlay.getValue()
-      }
-      data.snippets = [ itemData ] if actionType is "Insert"
-      return data
-
-    return itemData
+        snippets   : []
+      return groupData
+
+    else
+      editorValue = @aceEditor?.getValue()
+      snippet     = """new KDView({partial: "#{editorValue}"});""" if @snippetType is "Text"
+
+      itemData    =
+        introId    : inputs.IntroId.getValue()
+        introTitle : inputs.IntroTitle.getValue()
+        snippet    : snippet or editorValue
+
+      return itemData

   setVisibilityOfOverlayWarning: (isShowing) ->
     @warningView = @warningView or @fields?.warning.getSubViews()[1].getSubViews()[0]


Removes unnecessary comment.

Removes unnecessary comment.

--- keymaps/quick-file-actions.cson
+++ keymaps/quick-file-actions.cson
@@ -1,12 +1,3 @@
-# Keybindings require three things to be fully defined: A selector that is
-# matched against the focused element, the keystroke and the command to
-# execute.
-#
-# Below is a basic keybinding which registers on all platforms by applying to
-# the root workspace element.
-
-# For more detailed documentation see
-# https://atom.io/docs/latest/behind-atom-keymaps-in-depth
 'atom-workspace':
   'ctrl-alt-c': 'quick-file-actions:copy'
   'ctrl-alt-m': 'quick-file-actions:move'


Add sync Encrypt/Decrypt function

Add sync Encrypt/Decrypt function

--- kites/applications/utils.coffee
+++ kites/applications/utils.coffee
@@ -13,6 +13,8 @@
 coffee    = require 'coffee-script'
 nodePath  = require 'path'
 {exec}    = require 'child_process'
+
+crypto    = require 'crypto'

 # Custom Auth Error
 class AuthorizationError extends Error
@@ -90,6 +92,52 @@
     .replace(/[-]+/g, "-")        # replace multiple instances of the hyphen with a single instance
     .replace(/^-+|-+$/g, "")      # trim leading and trailing hyphens

+# Encrypt / Decrypt usage taken from http://stackoverflow.com/a/10550004/1370271
+
+encrypt = (input, password) ->
+
+  md5 = crypto.createHash("md5")
+  md5.update password
+
+  key = md5.digest("hex")
+  md5 = crypto.createHash("md5")
+  md5.update password + key
+
+  iv = md5.digest("hex")
+
+  data      = new Buffer(input, "utf8").toString("binary")
+  cipher    = crypto.createCipheriv("aes-256-cbc", key, iv.slice(0, 16))
+  encrypted = cipher.update(data, "binary") + cipher.final("binary")
+
+  new Buffer(encrypted, "binary").toString("base64")
+
+decrypt = (input, password) ->
+
+  # Convert urlsafe base64 to normal base64
+  input = input.replace(/\-/g, "+").replace(/_/g, "/")
+
+  # Convert from base64 to binary string
+  edata = new Buffer(input, "base64").toString("binary")
+
+  # Create key from password
+  md5 = crypto.createHash("md5")
+  md5.update password
+  key = md5.digest("hex")
+
+  # Create iv from password and key
+  md5 = crypto.createHash("md5")
+  md5.update password + key
+  iv = md5.digest("hex")
+
+  # Decipher encrypted data
+  decipher = crypto.createDecipheriv("aes-256-cbc", key, iv.slice(0, 16))
+  decrypted = decipher.update(edata, "binary") + decipher.final("binary")
+  new Buffer(decrypted, "binary").toString("utf8")
+
+# console.log "Encrypting..."
+# console.log "Encoded:", encoded = encrypt("gokmen", "askldfhlaksd")
+# console.log "Decoded:", decrypt(encoded, "askldfhlaksd")
+
 # Export them'all
 exports.normalizeUserPath  = normalizeUserPath
 exports.safeForUser        = safeForUser
@@ -99,6 +147,8 @@
 exports.getIds             = getIds
 exports.makedirp           = makedirp
 exports.slugify            = slugify
+exports.encrypt            = encrypt
+exports.decrypt            = decrypt

 # Errors
 exports.KodingError        = KodingError


HUD Fix

HUD Fix

--- modules/player/hud.coffee
+++ modules/player/hud.coffee
@@ -109,10 +109,10 @@
     #{@getDisplayName aby} swapped some items:
     ```fix
     * #{indexes[1]+1} -> #{indexes[0]+1}
-      #{items[1].title}
+      #{items[0].title}

     * #{indexes[0]+1} -> #{indexes[1]+1}
-      #{items[0].title}
+      #{items[1].title}
     ```
     """


more tweaks and bugfixes on error handling

--- src/base.iced
+++ src/base.iced
@@ -100,9 +100,9 @@
   #==============================================

   handle : (cb) ->
-    await @__handle_stage_1 defer err     # input & header processing
-    await @__handle_stage_2 err, defer()  # custom logic OR error handling
-    await @__handle_stage_3 defer()       # output
+    await @__handle_stage_1 defer e1       # input & header processing
+    await @__handle_stage_2 e1, defer e2   # custom logic OR error handling
+    await @__handle_stage_3 e2, defer()    # output
     cb()

   #------
@@ -160,33 +160,34 @@

   #------

-  _handle_err : (err, cb) -> cb()
-
-  #------
-
-  __handle_stage_2: (err, cb) ->
+  _handle_err : (cb, err) -> cb err
+
+  #------
+
+  __handle_stage_2: (e1, cb) ->
+    if e1?
+      await @_handle_err defer(e2), e1
+    else
+      await @_handle defer e2
+    cb e2
+
+  #------
+
+  __err_to_http_and_json : (err) ->
     if err?
-      await @_handle_err err, defer()
-    else
-      await @_handle defer err
-      if err?
-        code = err.sc or sc.GENERIC_ERROR
-        @set_error code, err.message
-        @http_out_code = c if (c = err.http_code)?
-    cb err
-
-  #------
-
-  __handle_stage_3 : (cb) ->
+      code = err.sc or sc.GENERIC_ERROR
+      @set_error code, err.message
+      @http_out_code = c if (c = err.http_code)?
+
+  #------
+
+  __handle_stage_3 : (err, cb) ->
     unless @response_sent_yet
+      @__err_to_http_and_json err
       await @send_res_json defer()
     cb()

   #==============================================
-
-  _handle_err : (cb) -> @_handle cb
-
-  #-----------------------------------------

   @make_endpoint : (opts) ->
     (req, res) =>