Game server error handling

Game server error handling

--- server.coffee
+++ server.coffee
@@ -121,8 +121,11 @@
         lobby.to(msg.gameid).emit('netrunner', {type: "games", games: games})

       when "do"
-        state = gameEngine.main.exec("do", msg)
-        lobby.to(msg.gameid).emit("netrunner", {type: "state", state: state})
+        try
+          state = gameEngine.main.exec("do", msg)
+          lobby.to(msg.gameid).emit("netrunner", {type: "state", state: state})
+        catch err
+          console.log(err)

 # Express config
 app.configure ->


Main classes and such, no data model yet

--- assets/javascripts/readlet-mini.coffee
+++ assets/javascripts/readlet-mini.coffee
@@ -9,3 +9,46 @@
 #= require backbone-min
 #= require tres

+class HomeScreen extends Tres.Screen
+  className : 'home-screen'
+  template  : _.template $('#home-template').html()
+
+  submit: (event) ->
+    event.preventDefault()
+    form = new Tres.Form @$el.find('form')
+    Tres.Router.go "search?query=#{form.attributes().query}"
+
+class ResultsScreen extends Tres.Screen
+  className : 'results-screen'
+  template  : _.template $('#results-template').html()
+
+  events:
+    'click .result': 'clickResult'
+    'click .back-button': 'goBack'
+
+  clickResult: (event) ->
+    $li = $ event.target
+    Tres.Router.go "article?name=#{$li.data('name')}"
+
+  goBack: ->
+    history.back()
+
+class ArticleScreen extends Tres.Screen
+  className : 'articles-screen'
+  template  : _.template $('#article-template').html()
+
+  events:
+    'click .back-button': 'goBack'
+
+  goBack: ->
+    history.back()
+
+$ ->
+  App = new Tres.App
+
+  App.on
+    ''                    : new HomeScreen
+    'search?query=:query' : new ResultsScreen
+    'article?name=:name'  : new ArticleScreen
+
+  App.boot()


add push

add push

--- assets/scripts/app/models/build.coffee
+++ assets/scripts/app/models/build.coffee
@@ -29,6 +29,7 @@
   # TODO add eventType to the api for api build requests
   eventType: (->
     # if @get('pullRequest') then 'pull_request' else 'push'
+    'push'
   ).property('pull_request')

   isPullRequest: (->


sitewide: small non-deterministic fix

sitewide: small non-deterministic fix

--- client/app/MainApp/ApplicationManager.coffee
+++ client/app/MainApp/ApplicationManager.coffee
@@ -172,7 +172,7 @@
     app = @get name
     cb  = (appInstance)-> appInstance?[command]? rest...

-    if app then cb app
+    if app then @utils.defer -> cb app
     else @create name, cb

   create:(name, params, callback)->


Improve toCodePoints and fromCodePoint if possible

--- src/trie.coffee
+++ src/trie.coffee
@@ -6,6 +6,15 @@
   result = []
   counter = 0
   length = str.length
+  if str.codepointat?
+    while counter < length
+      codepoint = str.codepointat counter
+      if codepoint?
+        result.push codepoint
+        counter += if codepoint > 0xffff then 2 else 1
+      else
+        return result
+    return result
   while counter < length
     value = str.charCodeAt counter++
     if counter < length and 0xD800 <= value <= 0xDBFF
@@ -20,7 +29,7 @@
   return result

 # punnycode.ucs2.encode from https://github.com/bestiejs/punycode.js
-fromCodePoint = (codepoints...) ->
+fromCodePoint = String.fromCodePoint or (codepoints...) ->
   fromCharCode = String.fromCharCode
   str = ''
   for value in codepoints


Client: Activity: Tests: Updated editPost tests selectors

--- client/test/lib/teams/teamslikeunlike.coffee
+++ client/test/lib/teams/teamslikeunlike.coffee
@@ -4,7 +4,7 @@
 HUBSPOT      = no

-module.exports =
+module.exports =

   likePost: (browser) ->


comments

comments

--- meteor/client/mtrPlayer.coffee
+++ meteor/client/mtrPlayer.coffee
@@ -22,6 +22,10 @@
   readyState: null
   networkState: null

+  # Add methods:
+  # stop()
+  # play()
+
   audioTag: ->
     $('#player')[0]

@@ -44,8 +48,8 @@

     @audioTag()?.pause()
     Meteor.setTimeout (=>
-      @audioTag().load()
-      @audioTag().play()
+      @audioTag()?.load()
+      @audioTag()?.play()
     ), 10

     location.href = '#' + channel


changed source for files component

changed source for files component

--- src/controller.main.coffee
+++ src/controller.main.coffee
@@ -100,7 +100,7 @@
   template: '#backup-files-template'
   data: ->
     files: dfc.selectedObject.backupCache
-    active: this.$root.active
+    active: dfc.selectedObject
     activeIndex: this.$root.activeIndex
     fsstats: this.fileStats this.files
   methods:


remove chunks in between click and hover

--- src/interface.coffee
+++ src/interface.coffee
@@ -152,6 +152,7 @@

     mostOuterToken.$token.removeClass("#{side}-end")
     $token = $(this.$tokens.get(targetIndex))
+    this.removeChunkWithIndex(targetIndex) if $token.data('token-id') >= 0
     $token.addClass("selected #{side}-end")

     if side == 'left'


Updated html5-parser (extractScope and extractNode)

--- src/html5-parser.coffee
+++ src/html5-parser.coffee
@@ -14,6 +14,7 @@
       attribute: [].concat ['$', '?'], Html5Parser.CommonTokens.urlSafeBase64
       equal: '='
       dquote: '"'
+      slash: '/'
       newline: Html5Parser.CommonTokens.newline
       whitespaceAndNewline: [].concat Html5Parser.CommonTokens.newline, Html5Parser.CommonTokens.whitespace
       angularBraceStart: '<'
@@ -78,8 +79,98 @@
     Extraction
   ###

-  extractScope: ->
-  	return ''
+  extractScope: (parentNode = null)->
+    scope = []
+    while node = @extractStatement parentNode
+      scope.push node
+    return scope
+
+  extractStatement: (parentNode)->
+
+    @ignoreWhitespaceAndNewline()
+
+    if node = @extractNode parentNode
+      return node
+
+    else
+      return false
+
+  extractNode: (parentNode)->
+
+    return false unless @take @charset['angularBraceStart']
+
+    @ignoreWhitespaceAndNewline()
+    tag = @takeAllUntil ([].concat @charset['whitespaceAndNewline'], @charset['angularBraceEnd'])
+
+    @ignoreWhitespaceAndNewline()
+    attributeMap = {}
+    while attribute = @takeAll @charset['attribute']
+
+      @ignoreWhitespaceAndNewline()
+      if @take @charset['equal']
+
+        @ignoreWhitespaceAndNewline()
+        if @take @charset['dquote']
+          value = @takeAllUntil @charset['dquote']
+          attributeMap[attribute] = value
+          @take @charset['dquote']
+        else
+          @throwError 'Expected Double Quote after = sign'
+
+      else
+        attributeMap[attribute] = null
+      @ignoreWhitespaceAndNewline()
+
+    @ignoreWhitespaceAndNewline()
+    node = new Html5Node parentNode, tag, attributeMap
+
+    console.log node
+
+    if tag in nonClosingHtml5TagList
+      unless @take @charset['angularBraceEnd']
+        @throwError 'Expected > at the end of non-closing tag ' + tag
+      node.isNonClosing = true
+      return node
+
+    if @take @charset['slash']
+      @ignoreWhitespaceAndNewline()
+      unless @take @charset['angularBraceEnd']
+        @throwError 'Expected > at the end of self closing tag' + tag + ' after /'
+      node.isSelfClosing = true
+      return node
+
+    if tag in selfClosingHtml5TagList
+      unless @take @charset['angularBraceEnd']
+        @throwError 'Expected > at the end of tag ' + tag
+      node.isSelfClosing = true
+      return node
+
+    unless @take @charset['angularBraceEnd']
+      @throwError 'Expected > at the end of tag ' + tag
+
+    node.childrenList = @extractScope node
+
+    @ignoreWhitespaceAndNewline()
+
+    unless @take @charset['angularBraceStart']
+      @throwError 'Expected <'
+
+    @ignoreWhitespaceAndNewline()
+
+    unless @take @charset['slash']
+      @throwError 'Expected /'
+
+    @ignoreWhitespaceAndNewline()
+
+    unless @take tag
+      @throwError 'Expected ' + tag
+
+    @ignoreWhitespaceAndNewline()
+
+    unless @take @charset['angularBraceEnd']
+      @throwError 'Expected >'
+
+    return node

 @Html5Parser = Html5Parser


fixed variable scoping issue with grouped data apps (tree map and stacked)

--- src/core/data/group.coffee
+++ src/core/data/group.coffee
@@ -1,5 +1,6 @@
 fetchValue = require "../fetch/value.js"
-# Groups data into groups to use with D3 layouts. Helps prevent key name mismatches (parent, child, value, etc).
+# Groups data into groups to use with D3 layouts. Helps prevent key name
+# mismatches (parent, child, value, etc).
 module.exports = (vars, data, nesting) ->

   groupedData = d3.nest()
@@ -7,8 +8,9 @@

   for n, i in nesting
     if i < vars.depth.value
-      key = n
-      groupedData.key (d) -> fetchValue vars, d.d3plus, key
+      do (n) ->
+        groupedData.key (d) ->
+          fetchValue vars, d.d3plus, n

   strippedData = []
   for d in data


improve tests for subscribing within dispatch loop

--- spec/BaconSpec.coffee
+++ spec/BaconSpec.coffee
@@ -1720,11 +1720,31 @@
         root = Bacon.once(0).toProperty()
         root.onValue ->
         Bacon.later(1).onValue ->
-          root.map(->).onValue ->
-            bus.push(1)
-            bus.end()
+          root.map(-> 1).subscribe (event) ->
+            if event.isEnd()
+              bus.end()
+            else
+              bus.push(event.value())
         bus
       [1])
+  describe "poking for errors 2", ->
+    expectStreamEvents(
+      ->
+        bus = new Bacon.Bus()
+        root = Bacon.sequentially(1, [1,2]).toProperty()
+        root.subscribe (event) ->
+        outdatedChild = root.filter((x) -> x == 1).map((x) -> x)
+        outdatedChild.onValue((x) -> console.log "set", x) # sets value but will be outdated at value 2
+
+        Bacon.later(3).onValue ->
+          outdatedChild.subscribe (event) ->
+            if event.isEnd()
+              bus.end()
+            else
+              bus.push(event.value())
+        bus
+      [1]
+    )

 describe "Bacon.combineAsArray", ->
   describe "initial value", ->


:fire: unused variable

:fire: unused variable

--- spec/tabs-spec.coffee
+++ spec/tabs-spec.coffee
@@ -622,7 +622,6 @@
     describe "when pane:close is fired", ->
       it "destroys all the tabs within the pane", ->
         pane2 = pane.splitDown(copyActiveItem: true)
-        [item2] = pane2.getItems()
         tabBar2 = new TabBarView
         tabBar2.initialize(pane2)
         tab2 = tabBar2.tabAtIndex(0)


Workspace: Removed subViews[0] to get active pane content.

--- client/landing/app/CommonViews/workspace/panes/collaborativetabbededitorpane.coffee
+++ client/landing/app/CommonViews/workspace/panes/collaborativetabbededitorpane.coffee
@@ -5,6 +5,7 @@
     super options, data

     @openedFiles      = []
+    @editors          = []
     @activeTabIndex   = 0

     @createEditorTabs()
@@ -26,14 +27,20 @@

     @workspaceRef.onDisconnect().remove()  if @workspace.amIHost()

+  getActivePaneEditor: ->
+    return @editors[@getActivePaneIndex()] or null
+
   getActivePaneContent: ->
-    return @tabView.getActivePane().subViews[0].getValue()
+    return @getActivePaneEditor().getValue()

   getActivePaneFileData: ->
-    return @tabView.getActivePane().subViews[0].getData()
+    return @getActivePaneEditor().getData()

   getActivePane: ->
     return @tabView.getActivePane()
+
+  getActivePaneIndex: ->
+    return @tabView.getPaneIndex @getActivePane()

   createEditorTabs: ->
     @tabHandleContainer = new ApplicationTabHandleHolder
@@ -77,6 +84,7 @@
     }

     pane.addSubView editor
+    @editors.push editor
     @tabView.addPane pane
     @activeTabIndex = @tabView.panes.length

@@ -90,6 +98,8 @@
     @workspaceRef.child("tabs").push workspaceRefData  unless sessionKey

     pane.on "KDTabPaneDestroy", =>
+      removedPaneIndex = @tabView.getPaneIndex pane
+      @editors.splice removedPaneIndex, 1
       @workspaceRef.once "value", (snapshot) =>
         {tabs} = snapshot.val()
         return unless tabs


added support for json content type

added support for json content type

--- Framework/Network.coffee
+++ Framework/Network.coffee
@@ -13,6 +13,9 @@
       url: null
       params: {}
       method: 'POST'
+      contentType: 'application/x-www-form-urlencoded'
+      userID: null
+      token: null
       onSuccess: ->
       onError: ->
       onRetry: (retryCount) ->
@@ -23,12 +26,20 @@

     @reset()
     @xhr.open('POST', options.url) #TODO: GJ: add support for GET
-    @xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded')
+    @xhr.setRequestHeader('Content-Type', options.contentType)
+
+    @xhr.setRequestHeader('MyHome-UserID', options.userID) if options.userID?
+    @xhr.setRequestHeader('MyHome-Token', options.token) if options.token?
+
     @xhr.onload = (e) ->
       Ti.API.info "@responseText: #{@responseText}"
       options.onSuccess(JSON.parse(@responseText))
     @xhr.onerror = () => @onError(options)
-    @xhr.send(options.params)
+
+    if options.contentType == 'application/json'
+      @xhr.send(JSON.stringify(options.params))
+    else
+      @xhr.send(options.params)

   reset: -> @xhr.abort()


payment: simplify the payment modal view structure.

--- client/Main/payment/modal.coffee
+++ client/Main/payment/modal.coffee
@@ -9,8 +9,10 @@

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

-    options.width     = 534
-    options.cssClass  = KD.utils.curry 'payment-modal', options.cssClass
+    options.title    = 'Upgrade your plan'
+    options.subtitle = 'And get some things you know such and such'
+    options.width    = 534
+    options.cssClass = KD.utils.curry 'payment-modal', options.cssClass

     { state } = options

@@ -18,46 +20,33 @@

     super options, data

-    @initScenes()
     @initViews()
-
-
-  initScenes: ->
-
-    @scenes = [
-      title      : 'Upgrade your plan'
-      subtitle   : 'And get some things you know such and such'
-      sceneClass : PaymentForm
-      events     : ->
-        @forwardEvent @scene, 'PaymentSubmitted'
-        @scene.forwardEvent this, 'PaymentProviderLoaded'
-    ,
-      title       : ''
-      subtitle    : ''
-      sceneClass : PaymentForm
-    ]
-
-
-  openScene: (sceneNumber, options = {}) ->
-
-    @scene?.destroy()
-
-    { title, subtitle, sceneClass, events } = @scenes[sceneNumber]
-
-    @setTitle title
-    @setSubtitle subtitle
-
-    options.state = @state
-
-    @scene = new sceneClass options
-
-    events.call this # coming from the events property of scene array
-
-    @addSubView @scene
+    @initEvents()

   initViews: ->
-    options = @getOptions()
-    @scene  = @openScene @initialState.scene, @state
+    @addSubView @errors = new KDCustomHTMLView
+      cssClass : 'errors hidden'
+
+    @addSubView @form = new PaymentForm { @state }

+  initEvents: ->
+    @on 'StripeRequestValidationFailed', @bound 'handleStripeFail'
+    @on 'PaymentFailed',                 @bound 'handleError'
+    @on 'PaymentSucceeded',              @bound 'handleSuccess'
+
+    @forwardEvent @form, 'PaymentSubmitted'
+    @form.forwardEvent this, 'PaymentProviderLoaded'
+
+
+  handleStripeFail: (error) ->
+    @form.showValidationErrorsOnInputs error
+
+
+  handleError: (error) ->
+
+
+  handleSuccess: ->
+
+


payment: remove duplicate event in payment modal

--- client/Main/payment/modal.coffee
+++ client/Main/payment/modal.coffee
@@ -50,8 +50,6 @@
     options.state = @state

     @scene = new sceneClass options
-    @scene.on "PaymentSubmitted", (formData)=>
-      @emit "PaymentSubmitted", formData

     events.call this # coming from the events property of scene array


Middleware refactoring

Middleware refactoring

--- client/modules/middleware.coffee
+++ client/modules/middleware.coffee
@@ -1,12 +1,7 @@

 # # Middleware

-i8n = require 'inflection'
-
-config = require 'config'
-pkgman = require 'pkgman'
-
-debug = require('debug') 'shrub:middleware'
+{EventEmitter} = require 'events'

 # ## Middleware
 #
@@ -24,7 +19,7 @@
 # Error-handling middleware are only called if a previous middleware threw or
 # passed an error. Conversely, non-error-handling middleware are skipped if a
 # previous error occurred.
-exports.Middleware = class Middleware
+exports.Middleware = class Middleware extends EventEmitter

 	# ## *constructor*
 	#
@@ -36,7 +31,7 @@
 	# *Add a middleware function to the stack.*
 	#
 	# * (function) `fn` - A middleware function.
-	use: (fn, label) -> @_middleware.push fn: fn, label: label
+	use: (fn) -> @_middleware.push fn

 	# ## ::dispatch
 	#
@@ -48,21 +43,18 @@
 	#   finished. If an error occurred, it will be passed as the first
 	#   argument.
 	dispatch: (args..., fn) ->
+		self = this

 		index = 0

-		invoke = (error) =>
+		invoke = (error) ->

-			if index > 0
-				if (label = @_middleware[index - 1].label)?
-					debug "Invoked `#{label}`."
+			self.emit 'invoked', self._middleware[index - 1] if index > 0

 			# Call `fn` with any error if we're done.
-			return fn error if index is @_middleware.length
+			return fn error if index is self._middleware.length

-			{fn: current, label} = @_middleware[index++]
-
-			debug "Invoking `#{label}`..." if label?
+			current = self._middleware[index++]

 			# Error-handling middleware.
 			if current.length is args.length + 2
@@ -75,7 +67,8 @@
 					try
 						localArgs = args.concat()
 						localArgs.unshift error
-						localArgs.push (error) -> invoke error
+						localArgs.push invoke
+						self.emit 'invoking', current
 						current localArgs...
 					catch error
 						invoke error
@@ -100,7 +93,8 @@
 					# the error and pass it along.
 					try
 						localArgs = args.concat()
-						localArgs.push (error) -> invoke error
+						localArgs.push invoke
+						self.emit 'invoking', current
 						current localArgs...
 					catch error
 						invoke error
@@ -108,12 +102,19 @@
 		# Kick things off.
 		invoke()

+i8n = require 'inflection'
+
+config = require 'config'
+pkgman = require 'pkgman'
+
+debug = require('debug') 'shrub:middleware'
+
 # ## fromHook
 #
 # Create a middleware stack from the results of a hook and path configuration.
 exports.fromHook = (hook, paths, args...) ->

-	middleware = new Middleware
+	middleware = new Middleware()

 	# Invoke the hook and `use` the middleware in the paths configuration
 	# order.
@@ -124,7 +125,7 @@

 		debug "- - #{spec.label}"

-		middleware.use _, spec.label for _ in spec.middleware ? []
+		middleware.use fn, spec.label for fn in spec.middleware ? []

 	middleware


removed unused lines of code

removed unused lines of code

--- core/app/backbone/views/facts/react_fact_vote_area.coffee
+++ core/app/backbone/views/facts/react_fact_vote_area.coffee
@@ -77,8 +77,6 @@
               disbelieves: votes.disbelieves
           _td ["fact-vote-amount-disbelieves"], votes.disbelieves

-
-
 window.ReactVoteArea = React.createBackboneClass
   _voting: ->
     _div className: 'fact-vote',


minor comment fix in popup.coffee

minor comment fix in popup.coffee

--- src/lib/popup.coffee
+++ src/lib/popup.coffee
@@ -46,7 +46,7 @@
     log.info 'Initializing the popup'
     analytics.track 'Frames', 'Displayed', 'Popup'

-    # Insert the prepared HTML in to the popup's body
+    # Insert the prepared HTML in to the popup's body.
     document.getElementById('templates').innerHTML = ext.templatesHtml

     # Find the required elements.


Changed internal find path functions to return the path of dir instead of the flash file

--- lib/flash-loader.coffee
+++ lib/flash-loader.coffee
@@ -62,7 +62,7 @@
         return null if chromeVersions.length is 0 # Broken Chrome...
         chromeVer = chromeVersions.reduce getNewerVersion
         chromeFlashPath = join chromeVersionsDir, chromeVer,
-          'Google Chrome Framework.framework/Internet Plug-Ins/PepperFlash', FILENAME
+          'Google Chrome Framework.framework/Internet Plug-Ins/PepperFlash'
         fs.accessSync chromeFlashPath
         chromeFlashPath
       catch
@@ -74,7 +74,7 @@
   switch PLATFORM
     when 'darwin'
       try
-        systemFlashPath = '/Library/Internet Plug-Ins/PepperFlashPlayer/PepperFlashPlayer.plugin'
+        systemFlashPath = '/Library/Internet Plug-Ins/PepperFlashPlayer'
         fs.accessSync systemFlashPath
         systemFlashPath
       catch


Small bug fix

Small bug fix

--- lib/latexer-hook.coffee
+++ lib/latexer-hook.coffee
@@ -64,10 +64,7 @@
           return
         lineCount = @editor.getLineCount()
         preText= @editor.getTextInBufferRange([[0,0], [pos[0],0]])
-        if pos[0] < lineCount
-          remainingText = @editor.getTextInBufferRange([[pos[0]+1,0],[lineCount+1,0]])
-        else
-          remainingText = ""
+        remainingText = @editor.getTextInBufferRange([[pos[0],0],[lineCount+1,0]])
         balanceBefore = (preText.match(beginTextRex)||[]).length - (preText.match(endTextRex)||[]).length
         balanceAfter = (remainingText.match(beginTextRex)||[]).length - (remainingText.match(endTextRex)||[]).length
         remainingOnPrevLine = previousLine.substring(previousLine.indexOf(beginText))


:art: Simplify an if in updateLineMessages

--- lib/linter-views.coffee
+++ lib/linter-views.coffee
@@ -128,8 +128,7 @@
     return if @currentLine is line
     @currentLine = line
     @lineMessages = []
-    activeEditorLinter = @linter.getActiveEditorLinter()
-    if activeEditorLinter
+    if @linter.getActiveEditorLinter()
       @messages.forEach (message) =>
         if message.currentFile and message.range?.intersectsRow @currentLine
           @lineMessages.push message


Added option to prevent calling router.next during signInRequired().

--- src/AccountsUi.coffee
+++ src/AccountsUi.coffee
@@ -12,11 +12,13 @@
     if Router
       Router.route(loginRoute, {path: loginRoute, template: loginTemplate})

-  signInRequired: (router) ->
-    config = @_config
+  signInRequired: (router, args) ->
+    args = Setter.merge({
+      callNext: true
+    }, args)
     user = Meteor.user()
     @goToLogin() unless user
-    router.next()
+    if args.callNext then router.next()

   onAfterLogin: -> @_config.afterLogin?()


added base Attribute class.

added base Attribute class.

--- src/Attributes.coffee
+++ src/Attributes.coffee
@@ -7,6 +7,86 @@

 class Attribute extends Stateful

+	@addState 'empty',
+		transitions:
+			initial: true
+			exit:    'ready, dirty'
+
+	@addState 'ready',
+		transitions:
+			enter:   'dirty, conflicted, empty'
+			exit:    'dirty'
+
+	@addState 'dirty',
+		transitions:
+			enter:   'empty, ready, conflicted'
+			exit:    'ready, conflicted'
+
+	@addState 'conflicted',
+		transitions:
+			enter:   'dirty'
+			exit:    'dirty, ready'
+
+	@define "name",       get: -> @config.name
+	@define "owner",      get: -> @config.owner
+	@define "readonly",   get: -> if @config.readonly? then @config.readonly else false
+	@define "required",   get: -> if @config.isRequired? then @config.isRequired else false
+	@define "transient",  get: -> if @config.isTransient? then @config.isTransient else false
+
+	constructor: (config) ->
+		super(config)
+
+		@type = window[@config.type] ? __rubicon.models[@config.type]
+
+		@value    = undefined
+		@previous = undefined
+
+		# TODO add support for validation
+
+		@set(@config.default) if @config.default?
+
+	raw: -> throw new Error("Override raw() on #{this}")
+	get: -> throw new Error("Override get() on #{this}")
+	set: (value) ->
+		throw new Error "Can't set an attribute that isn't loaded." if @is 'empty'
+		throw new Error "Can't set a readonly attribute." if @readonly
+
+		isDiff = @_applyValue value
+		if isDiff
+			@state = 'dirty'
+			@_emitChange @value, @previous
+
+	update: (value, metadata) ->
+		isDiff = @_applyValue value
+		if isDiff
+			if @is 'empty' then @state = 'ready'
+			else if @is 'dirty' then @state = 'conflicted'
+
+			@_emitChange @value, @previous, metadata
+
+	rollback: ->
+		return this unless @is "dirty"
+
+		@value = @previous # even if undefined I think
+		@previous = undefined
+
+		@state = 'ready'
+		@_emitChange @value, @previous
+
+		return this
+
+	commit: ->
+		return this unless @is "dirty"
+
+		@previous = undefined # not sure why we do this here?
+		@state = 'ready'
+
+		return this
+
+	_applyValue: (value) -> throw new Error "Override _applyValue on #{@}"
+	_emitChange: (newValue, oldValue, metadata) -> @emit "change", newValue, oldValue, metadata
+
+
 exports.Attribute = Attribute

 class Property extends Attribute


add links to gcal me list

add links to gcal me list

--- src/google-calendar.coffee
+++ src/google-calendar.coffee
@@ -106,8 +106,10 @@
           start = moment(start).format(format)
           end = moment(end).format(format)

-          entry =  "[#{start}-#{end}] '#{item.summary}'"
+          summaryLink = "#{item.summary}"
+          entry =  "[#{start}-#{end}] #{summaryLink}"
           entry += " (#{item.location})" if item.location
+          entry += " hangout" if item.hangoutLink
           entry
         ).join("\n")
         console.log items


Call functions

Call functions

--- workers/librato/lib/librato/index.coffee
+++ workers/librato/lib/librato/index.coffee
@@ -91,7 +91,6 @@
       load5,
       load15,
       # Memory
-      memory_total,
       memory_used,
       # Users
       total_users,
@@ -144,10 +143,10 @@

 # Collect data from Mongo in every <interval>/2 seconds
 setInterval ->
-  collect_mongo
+  collect_mongo()
 , (librato.interval / 2)

 # Post to Librato in every <interval> seconds
 setInterval ->
-  post_to_librato
+  post_to_librato()
 , librato.interval


add header

add header

--- RainbowZooRESTAPI/rainbowZooServer.coffee
+++ RainbowZooRESTAPI/rainbowZooServer.coffee
@@ -1,3 +1,11 @@
+# raibowZooServer.coffee
+#
+# created by Radjiv Carrere
+# 1 november 2014
+#
+# License MIT
+#
+
 express = require("express")
 unicorn = require("./routes/unicorns")
 app = express()


Fixed server list view

Fixed server list view

--- admin/static/coffee/servers/server.coffee
+++ admin/static/coffee/servers/server.coffee
@@ -32,11 +32,7 @@
                                         ).filter( (replica) ->
                                             replica('server').eq(server("name"))
                                     )
-                                ).filter( (shard) ->
-                                    shard.isEmpty().not()
-                                ).concatMap( (roles) -> roles
                                 ).coerceTo('array')
-
                             )
                         ).filter( (table) ->
                             table("shards").isEmpty().not()


Correct behaviour of expander arrow swap when clicking outside element.

--- app/assets/javascripts/postings.js.coffee
+++ app/assets/javascripts/postings.js.coffee
@@ -117,7 +117,7 @@
       if !$(event.target).closest(".dropdown").length
         if $(".dropdown ul").is(":visible")
           $(".dropdown ul").slideUp(400)
-          $(this).parent().find("button").expanderSwap()
+          $(".dropdown ul").parent().find("button").expanderSwap()

     # Show and hide postings according to user's filter
     $(".filter-buttons a").click (event) ->


Update raptor.coffee

Update raptor.coffee

--- app/lib/field-guide-content/raptor.coffee
+++ app/lib/field-guide-content/raptor.coffee
@@ -1,7 +1,7 @@
 module.exports =
   description: '''

Raptors, also known as birds of prey, are divided into two main groups, the diurnal (day flying) species and the nocturnal (night flying) species, commonly known as owls. All raptors have clawed feet and sharply curved beaks, which are well-adapted for feeding on meat.

-
Diurnal raptors are further classified into the following groups: New World Vultures (Cathartidae); Falcons and Caracaras (Falconidae); Hawks and Eagles (Accipitridae); Osprey (Pandionidae); Secretary bird (Sagittariidae). Nocturnal raptors are further classified into the following groups: Barn Owls (Tytonidae); Eared Owls or Typical Owls (Strigidae).

+
Diurnal raptors are further classified into the following groups: New World Vultures (Cathartidae</>); Falcons and Caracaras (Falconidae); Hawks and Eagles (Accipitridae); Osprey (Pandionidae); Secretary bird (Sagittariidae). Nocturnal raptors are further classified into the following groups: Barn Owls (Tytonidae); Eared Owls or Typical Owls (Strigidae).

   '''

   scientificName: ''
@@ -12,9 +12,6 @@
   information: [{
     label: 'Length'
     value: '14 cm to 1.40 m, depending on species'
-  }, {
-    label: 'Height'
-    value: ''
   }, {
     label: 'Weight'
     value: '35 g to 12.25 kg, depending on species'


An improvement for containingString in #133.

--- app/lib/world/script_event_prereqs.coffee
+++ app/lib/world/script_event_prereqs.coffee
@@ -10,7 +10,7 @@
     return false if ap.greaterThanOrEqualTo? and not (v >= ap.greaterThanOrEqualTo)
     return false if ap.lessThan? and not (v < ap.lessThan)
     return false if ap.lessThanOrEqualTo? and not (v <= ap.lessThanOrEqualTo)
-    return false if ap.containingString? and v.search(ap.containingString) == -1
-    return false if ap.notContainingString? and v.search(ap.containingString) != -1
+    return false if ap.containingString? and v?.search(ap.containingString) == -1
+    return false if ap.notContainingString? and v?.search(ap.containingString) != -1

   return true


Individual page now loads when visited from other page

--- app/scripts/controllers/individual.coffee
+++ app/scripts/controllers/individual.coffee
@@ -95,6 +95,9 @@
 ##############
 ## Initial Calls
 ##############
-
+    $scope.rep = $scope.reps[$scope.selected.rep1.bioguide_id]
+    $scope.get_transparencydata_id($scope.selected.rep1.bioguide_id)
+    ApiGet.littleSis "entities/bioguide_id/#{$scope.selected.rep1.bioguide_id}.json?", $scope.callback_littleSis_id, this, $scope.selected.rep1.bioguide_id
+    ApiGet.nyt "members/#{$scope.selected.rep1.bioguide_id}", $scope.callback_nyt, this, $scope.selected.rep1.bioguide_id

   ])


Fix -> lowercase a variable name

Fix -> lowercase a variable name

--- test/integration/product-import.spec.coffee
+++ test/integration/product-import.spec.coffee
@@ -128,14 +128,14 @@
       { level: 'debug', stream: process.stdout }
     ]

-Config =
+config =
   clientConfig: ClientConfig
   errorLimit: 0

 describe 'Product Importer integration tests', ->

   beforeEach (done) ->
-    @import = new ProductImport logger, Config
+    @import = new ProductImport logger, config

     @client = @import.client


Updated keybindings to support Win32 and Linux

--- keymaps/csso.cson
+++ keymaps/csso.cson
@@ -1,3 +1,8 @@
 'atom-workspace':
   'ctrl-shift-o': 'csso:minify'
+
+'.platform-darwin atom-workspace':
   'ctrl-option-o': 'csso:restructure'
+
+'.platform-win32 atom-workspace, .platform-win32 atom-workspace':
+  'ctrl-option-o': 'csso:restructure'


Don't try to copy items by serializing and deserializing them

--- lib/tab-bar-view.coffee
+++ lib/tab-bar-view.coffee
@@ -196,11 +196,8 @@

   splitTab: (fn) ->
     if item = @rightClickedTab?.item
-      if copiedItem = @copyItem(item)
+      if copiedItem = item.copy?()
         @pane[fn](items: [copiedItem])
-
-  copyItem: (item) ->
-    item.copy?() ? atom.deserializers.deserialize(item.serialize())

   closeOtherTabs: (active) ->
     tabs = @getTabs()


Extract file checking into readable helper methods

--- lib/tests_finder.coffee
+++ lib/tests_finder.coffee
@@ -18,24 +18,32 @@
   _search_directory: (dir, files) ->
     # TODO: make this return files, instead of using a param for that.
     for file in fs.readdirSync(dir)
-
-      # Ignore hidden stuff.
-      continue if file[0] == '.'
-
+      continue if @_is_hidden file
       filePath = path.resolve "#{dir}/#{file}"
       stat = fs.statSync filePath
       if stat.isFile()
-
-        # Ignore non-test code files.
-        continue unless file.match /^.*_test\.[^\.]+$/
-
-        # Ignore non-code files.
-        continue unless file.match /(js|coffee)$/
-
+        continue unless @_is_test_file file
         files.push filePath
       else if stat.isDirectory()
         @_search_directory filePath, files

+  # Returns whether the given filesystem object is hidden.
+  _is_hidden: (file) ->
+    file[0] == '.'
+
+
+  # Returns whether the file with the given filename contains unit tests.
+  _is_test_file: (file) ->
+
+    # Ignore non-test code files.
+    return false unless file.match /^.*_test\.[^\.]+$/
+
+    # Ignore non-code files.
+    return false unless file.match /(js|coffee)$/
+
+    true
+
+

 module.exports = TestsFinder


Use atom.themes.onDidReloadAll

Use atom.themes.onDidReloadAll

--- lib/themes-panel.coffee
+++ lib/themes-panel.coffee
@@ -3,7 +3,7 @@
 fs = require 'fs-plus'
 fuzzaldrin = require 'fuzzaldrin'
 _ = require 'underscore-plus'
-{$$, View, TextEditorView} = require 'atom'
+{$$, CompositeDisposable, View, TextEditorView} = require 'atom'

 AvailablePackageView = require './available-package-view'
 ErrorView = require './error-view'
@@ -65,6 +65,7 @@

   initialize: (@packageManager) ->
+    @disposables = new CompositeDisposable()
     @packageViews = []
     @loadPackages()

@@ -78,7 +79,7 @@
     @subscribe @packageManager, 'theme-installed', =>
       @populateThemeMenus()

-    @subscribe atom.themes, 'reloaded', => @updateActiveThemes()
+    @disposables.add atom.themes.onDidReloadAll => @updateActiveThemes()
     @updateActiveThemes()

     @filterEditor.getEditor().onDidStopChanging => @matchPackages()
@@ -90,6 +91,9 @@
     @uiMenu.change =>
       @activeUiTheme = @uiMenu.val()
       @scheduleUpdateThemeConfig()
+
+  beforeRemove: ->
+    @disposables.dispose()

   filterThemes: (packages) ->
     packages.dev = packages.dev.filter ({theme}) -> theme


viewer contextmenu item fixed

viewer contextmenu item fixed

--- client/app/MainApp/filetree/controllers/findertreecontroller.coffee
+++ client/app/MainApp/filetree/controllers/findertreecontroller.coffee
@@ -112,7 +112,7 @@
       {nickname} = KD.whoami().profile
       appManager.notify "File must be under: /#{nickname}/Sites/#{nickname}.#{location.hostname}/website/"
     else
-      appManager.openFileWithApplication publicPath, "Viewer.kdapplication"
+      appManager.openFileWithApplication publicPath, "Viewer"

   refreshFolder:(nodeView, callback)->


Add password prompt when requiresAuthKey is set

--- lib/pg-hoff-list-servers-view.coffee
+++ lib/pg-hoff-list-servers-view.coffee
@@ -1,4 +1,5 @@
 Promise = require('promise')
+PgHoffDialog = require('./pg-hoff-dialog')
 PgHoffServerRequest     = require './pg-hoff-server-request'

 class PgHoffListServersView
@@ -11,16 +12,25 @@

     connect: (panel) ->
         listServersView = @
+        selectedServer = null
         return PgHoffServerRequest.Get 'listservers'
             .then (servers) ->
                 promise = listServersView.update(servers, listServersView.element)
                 panel.show()

                 return promise
-            .then (selectedServer) ->
+            .then (server) ->
+                selectedServer = server
+                requiresAuthKey = selectedServer.requiresauthkey == 'True' || selectedServer.requiresauthkey == '"True"'
+
+                if requiresAuthKey
+                    return PgHoffDialog.PromptPassword('Enter Password')
+                else
+                    return ''
+            .then (password) ->
                 request =
                     alias: selectedServer.alias,
-                    authkey: ''
+                    authkey: password

                 return PgHoffServerRequest.Post 'connect', request
             .then (response) ->


Inverted boolean for rendering in CheckVisible.

--- common/static/coffee/spec/discussion/view/new_post_view_spec.coffee
+++ common/static/coffee/spec/discussion/view/new_post_view_spec.coffee
@@ -10,8 +10,8 @@
         )
         @discussion = new Discussion([], {pages: 1})

-    checkVisibility = (view, expectedVisible, expectedDisabled, noRender) =>
-      if !noRender
+    checkVisibility = (view, expectedVisible, expectedDisabled, render) =>
+      if render
         view.render()
       # Can also be undefined if the element does not exist.
       expect(view.$('.group-selector-wrapper').is(":visible") or false).toEqual(expectedVisible)
@@ -51,28 +51,28 @@
         )

       it "is not visible to students", ->
-        checkVisibility(@view, false)
+        checkVisibility(@view, false, false, true)

       it "allows TAs to see the cohort selector", ->
         DiscussionSpecHelper.makeTA()
-        checkVisibility(@view, true)
+        checkVisibility(@view, true, false, true)

       it "allows moderators to see the cohort selector", ->
         DiscussionSpecHelper.makeModerator()
-        checkVisibility(@view, true)
+        checkVisibility(@view, true, false, true)

       it "only enables the cohort selector when applicable", ->
         DiscussionSpecHelper.makeModerator()
         # We start on the cohorted discussion
-        checkVisibility(@view, true)
+        checkVisibility(@view, true, false, true)
         # Select the uncohorted topic
         $('.topic-title:contains(General)').click()
         # The menu should now be visible but disabled.
-        checkVisibility(@view, true, true, true)
+        checkVisibility(@view, true, true, false)
         # Select the cohorted topic again
         $('.topic-title:contains(Topic)').click()
         # It should be visible and enabled once more.
-        checkVisibility(@view, true, false, true)
+        checkVisibility(@view, true, false, false)

       it "allows the user to make a cohort selection", ->
         DiscussionSpecHelper.makeModerator()
@@ -119,20 +119,20 @@
       it "disables the cohort menu if it is set false", ->
         DiscussionSpecHelper.makeModerator()
         @view.is_commentable_cohorted = false
-        checkVisibility(@view, true, true)
+        checkVisibility(@view, true, true, true)

       it "enables the cohort menu if it is set true", ->
         DiscussionSpecHelper.makeModerator()
         @view.is_commentable_cohorted = true
-        checkVisibility(@view, true)
+        checkVisibility(@view, true, false, true)

       it "is not visible to students when set false", ->
         @view.is_commentable_cohorted = false
-        checkVisibility(@view, false)
+        checkVisibility(@view, false, false, true)

       it "is not visible to students when set true", ->
         @view.is_commentable_cohorted = true
-        checkVisibility(@view, false)
+        checkVisibility(@view, false, false, true)

     describe "cancel post resets form ", ->
       beforeEach ->


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/color.coffee
+++ src/color.coffee
@@ -123,7 +123,7 @@
     y: 20
     width: 20
     height: 20
-    color: individualRgb
+    color: arrayRgb

   hex = Color('#ff0000')

@@ -132,12 +132,12 @@
     y: 20
     width: 20
     height: 20
-    color: individualRgb
+    color: hex

   rgb = Color('rgb(0, 255, 0)')

   canvas.drawRect
-    x: 130
+    x: 110
     y: 20
     width: 20
     height: 20
@@ -146,7 +146,7 @@
   hsl = Color('hsl(180, 1, 0.5)')

   canvas.drawRect
-    x: 160
+    x: 140
     y: 20
     width: 20
     height: 20


Get options string

Get options string

--- src/install/options.coffee
+++ src/install/options.coffee
@@ -14,8 +14,9 @@

 opts.push require './srvdb'

-module.exports =
 r = {}
 for z in opts when 'object' == typeof z
   for k, v of z when 'object' != typeof v
     r["DIR#{k.toUpperCase()}"] = v
+
+module.exports = ("#{n}=#{r[n]}" for n in Object.keys(r).sort()).join ' '


download config right away

download config right away

--- src/coffee/app.coffee
+++ src/coffee/app.coffee
@@ -2,6 +2,7 @@
 SwanKiosk =
   Interpreters: {}
   Components:   {}
+  Config:       {}
   Controllers:
     _find: (name) ->
       _.find this, (value, key) ->
@@ -15,3 +16,9 @@

 # on page load
 $ SwanKiosk.init
+
+$.ajax(
+  url: '/assets/config/kiosk-config.json'
+  async: false
+).done (data) ->
+  SwanKiosk.Config = data


chroma.scale accepting bezier interpolator as colors

--- src/colorscale.coffee
+++ src/colorscale.coffee
@@ -55,19 +55,20 @@
             colors = ['#ddd', '#222']
         if colors? and type(colors) == 'string' and chroma.brewer?[colors]?
             colors = chroma.brewer[colors]
-        # make a copy of the colors
-        colors = colors.slice(0)
-        # convert to chroma classes
-        for c in [0..colors.length-1]
-            col = colors[c]
-            colors[c] = chroma(col) if type(col) == "string"
-        # auto-fill color position
-        if positions?
-            _pos = positions
-        else
-            _pos = []
+        if type(colors) == 'array'
+            # make a copy of the colors
+            colors = colors.slice(0)
+            # convert to chroma classes
             for c in [0..colors.length-1]
-                _pos.push c/(colors.length-1)
+                col = colors[c]
+                colors[c] = chroma(col) if type(col) == "string"
+            # auto-fill color position
+            if positions?
+                _pos = positions
+            else
+                _pos = []
+                for c in [0..colors.length-1]
+                    _pos.push c/(colors.length-1)
         _colors = colors

     setDomain = (domain = []) ->
@@ -111,18 +112,21 @@
             f = f0 = (val - _min) / (_max - _min)
             f = Math.min(1, Math.max(0, f))

-        for i in [0.._pos.length-1]
-            p = _pos[i]
-            if f <= p
-                col = _colors[i]
-                break
-            if f >= p and i == _pos.length-1
-                col = _colors[i]
-                break
-            if f > p and f < _pos[i+1]
-                f = (f-p)/(_pos[i+1]-p)
-                col = chroma.interpolate _colors[i], _colors[i+1], f, _mode
-                break
+        if type(_colors) == 'array'
+            for i in [0.._pos.length-1]
+                p = _pos[i]
+                if f <= p
+                    col = _colors[i]
+                    break
+                if f >= p and i == _pos.length-1
+                    col = _colors[i]
+                    break
+                if f > p and f < _pos[i+1]
+                    f = (f-p)/(_pos[i+1]-p)
+                    col = chroma.interpolate _colors[i], _colors[i+1], f, _mode
+                    break
+        else if type(_colors) == 'function'
+            col = _colors f
         col

     setColors colors, positions


starting documentation...

starting documentation...

not a very good start! ahahah

--- src/modes/normal.coffee
+++ src/modes/normal.coffee
@@ -1,3 +1,9 @@
+# normal mode commands can take the following forms:
+#     [count] command
+# or
+#     [count] motion
+# or
+#     [count] operator [count] motion
 define (require, exports, module) ->
   motions = require 'jim/motions'
   util    = require 'jim/util'


activity: fix channel by participants query param

--- client/app/lib/socialapicontroller.coffee
+++ client/app/lib/socialapicontroller.coffee
@@ -828,7 +828,7 @@
     byParticipants: (options, callback) ->

       serialized = options.participants
-        .map (id) -> "participants=#{id}"
+        .map (id) -> "id=#{id}"
         .join "&"

       doXhrRequest


Cleanup

Cleanup

--- client.coffee
+++ client.coffee
@@ -14,7 +14,6 @@

 window.redraw = Obs.create(0) # For full redraw
 window.indicationArrowRedraw = Obs.create(0) # For indication arrow redraw
-window.updateRanking = Obs.create(0) # For updating the rankings of the score page (full page redraw)

 # ========== Events ==========
 exports.render = ->
@@ -596,7 +595,6 @@
 			paddingLeft: "14px"
 		Dom.h1 "Teamscores"
 	Ui.list !->
-		updateRanking.get()
 		Dom.style
 			padding: '0'
 		Db.shared.iterate 'game', 'teams', (team) !->


Fixed: recurring end date was not in UTC

--- client/app/views/event_modal_rrule.coffee
+++ client/app/views/event_modal_rrule.coffee
@@ -36,14 +36,14 @@

         options = RRule.fromString(@model.get('rrule')).options
-
+
         rrule =
             freq: options.freq
             interval: options.interval

         if options.until
             rrule.endMode = 'until'
-            rrule.until = moment(options.until).format @inputDateFormat
+            rrule.until = moment.tz(options.until, 'UTC').format @inputDateFormat
             rrule.count = ''

         else if options.count
@@ -80,7 +80,6 @@
             RRule.TH, RRule.FR, RRule.SA]

         options =
-            dtstart: start.toDate()
             freq: +@$('#rrule-freq').val()
             interval: +@$('#rrule-interval').val()

@@ -108,7 +107,7 @@
             when 'count'
                 options.count = +@$('#rrule-count').val()
             when 'until'
-                options.until = moment @$('#rrule-until').val(), @inputDateFormat
+                options.until = moment.tz @$('#rrule-until').val(), @inputDateFormat, 'UTC'
                     .toDate()

         new RRule options
@@ -158,4 +157,4 @@
             dayNames: locale._weekdays
             monthNames: locale._months
         @$('#rrule-help').html @getRRule().toText(window.t, language)
-        return true+        return true


SyncLocal: add vmName to fetchFileContent method

--- client/Main/synclocalcontroller.coffee
+++ client/Main/synclocalcontroller.coffee
@@ -101,10 +101,10 @@
       @emit "fs.save.finished", err, res
       callback? err, res

-  fetchFileContents: (fileName, callback)->
+  fetchFileContents: (vmName, fileName, callback)->
     KD.singletons.vmController.run
       method    : 'fs.readFile'
-      vmName    : "vm-1.sinan.koding.kd.io"
+      vmName    : vmName
       withArgs  :
         path    : FSHelper.plainPath fileName
     , (err, response)->


Changes based on reviews in merge request #767

--- client/app/MainApp/LocalStorage.coffee
+++ client/app/MainApp/LocalStorage.coffee
@@ -1,14 +1,14 @@
 class LocalStorage extends AppStorage

-  fetchStorage:(callback=noop)->
-    callback window["localStorage"][@getSignature()]
+  storage = window.localStorage
+
+  fetchStorage:->
     KD.utils.defer => @emit "ready"

   getValue: (key)->
     data = @_storageData[key]
     return data  if data
-
-    data = window["localStorage"][@getSignature key]
+    data = storage[@getSignature key]
     if data
       try
         data = JSON.parse data
@@ -37,12 +37,12 @@
     callback? @getValue key

   setValue: (key, value, callback)->
-    @_storageData[key] = value
-    window["localStorage"][@getSignature key] = JSON.stringify value
+    @_storageData[key] = value or ''
+    storage[@getSignature key] = (JSON.stringify value) or ''
     KD.utils.defer => callback? null

   unsetKey: (key, callback)->
-    delete window["localStorage"][@getSignature key]
+    delete storage[@getSignature key]
     delete @_storageData[key]

   getSignature:(key)->


remove debug dependency

remove debug dependency

--- src/index.coffee
+++ src/index.coffee
@@ -1,5 +1,3 @@
-debug = require("debug")("oscilloscope")
-
 class Oscilloscope

   constructor: (source, options = { }) ->
@@ -11,10 +9,8 @@
       throw new Error "Signal must be an AudioNode"

     if source instanceof AnalyserNode
-      debug "add signal analyser"
       @analyser = source
     else
-      debug "add signal source"
       @analyser = source.context.createAnalyser()
       source.connect @analyser

@@ -30,12 +26,10 @@
   # begin default signal animation
   animate: ->
     return if @drawRequest
-    debug "start animation"
     @drawRequest = window.requestAnimationFrame @_drawSignal

   # todo: stop signal animation
   stop: ->
-    debug "stop animation"
     cancelAnimationFrame @drawRequest
     @drawRequest = 0
     #@drawContext.clearRect 0, 0, @canvas.width, @canvas.height


Add image and description to VK

Add image and description to VK

--- vendor/assets/javascripts/socialAdapters.js.coffee
+++ vendor/assets/javascripts/socialAdapters.js.coffee
@@ -8,7 +8,7 @@
       @$selector = @$container.find(@settings.selectors[@type])

     if @$selector.length
-      @url = @settings.url
+      @url = encodeURIComponent @settings.url
       @title = encodeURIComponent @settings.title
       @description = encodeURIComponent @settings.description
       @image = encodeURIComponent @settings.image
@@ -139,5 +139,5 @@
   initClick: ->
     @$selector.on "click.#{@PLUGIN_NAME}", (e) =>
       e.preventDefault()
-      title = encodeURIComponent @settings.title
-      open "https://vk.com/share.php?url=#{@url}&title=#{@title}", "_blank", "scrollbars=0, resizable=1, menubar=0, left=100, top=100, width=550, height=440, toolbar=0, status=0"
+      params = "url=#{@url}&title=#{@title}&description=#{@description}&image=#{@image}&noparse=true"
+      open "https://vk.com/share.php?#{params}", "_blank", "scrollbars=0, resizable=1, menubar=0, left=100, top=100, width=550, height=440, toolbar=0, status=0"


Workspace: Fixed panel button callbacks.

--- client/app/CommonViews/workspace/panel/panel.coffee
+++ client/app/CommonViews/workspace/panel/panel.coffee
@@ -30,9 +30,10 @@
       partial   : """#{title}"""

   createHeaderButtons: ->
-    for buttonOptions in @getOptions().buttons
+    @getOptions().buttons.forEach (buttonOptions) =>
       buttonView = new KDButtonView
         title    : buttonOptions.title
+        cssClass : buttonOptions.cssClass
         callback : =>
           buttonOptions.callback @, @getDelegate()


Remove previous implementation

Remove previous implementation

--- client/ide/lib/views/chat/idechatmessagepane.coffee
+++ client/ide/lib/views/chat/idechatmessagepane.coffee
@@ -322,14 +322,3 @@
     @onboarding?.destroy()
     @onboarding = null

-
-  addParticipant: (options, participant) ->
-
-    return super options, participant  unless @videoActive
-
-    appManager = kd.getSingleton 'appManager'
-
-    appManager.tell 'IDE', 'canUserStartVideo', =>
-      super options, participant
-    , @videoActive
-


Editor: Remove modified from tab when file content synced.

--- client/ide/lib/workspace/panes/ideeditorpane.coffee
+++ client/ide/lib/workspace/panes/ideeditorpane.coffee
@@ -99,6 +99,7 @@

         @file.emit 'FileActionHappened', { eventName, content, cursor, from }

+    ace.on 'FileContentRestored', => @file.emit 'FileContentRestored'

     @file.on 'FileActionHappened', (data) =>
       { from, eventName } = data
@@ -108,14 +109,16 @@
         @updateContent data.content
         @aceView.showModifiedIconOnTabHandle()

+    @file.on 'FileContentRestored', => ace.removeModifiedFromTab()
+
     @file.on 'fs.save.finished', =>
       @aceView.removeModifiedFromTab @file.path
       @updateFileModifiedTime()

     @file.on 'FileContentsNeedsToBeRefreshed', =>
-      @getAce().fetchContents (err, content) =>
+      ace.fetchContents (err, content) =>
         @updateContent content
-        @aceView.removeModifiedFromTab @file.path
+        ace.removeModifiedFromTab()
         @updateFileModifiedTime()
         @contentChangedWarning?.destroy()
         @contentChangedWarning = null


ide: remove unused variables

ide: remove unused variables

--- client/ide/lib/workspace/panes/idefinderpane.coffee
+++ client/ide/lib/workspace/panes/idefinderpane.coffee
@@ -16,7 +16,6 @@
     super options, data

     appManager  = kd.getSingleton 'appManager'
-    computeCtrl = kd.getSingleton 'computeController'

     appManager.open 'Finder', (finderApp) =>
       fc = @finderController = finderApp.create


Refreshes required property on component update.

--- picoCTF-web/web/coffee/front-page.coffee
+++ picoCTF-web/web/coffee/front-page.coffee
@@ -121,6 +121,9 @@
   componentDidMount: ->
     $("input").prop 'required', true

+  componentDidUpdate: ->
+    $("input").prop 'required', true
+
   render: ->
     links =
     username: @linkState "username"


#508 - Hand-coded skip logic box doesn't open

--- dkobo/koboform/static/js/xlform/model_xlform.coffee
+++ dkobo/koboform/static/js/xlform/model_xlform.coffee
@@ -531,12 +531,20 @@

   switchEditingMode: () ->
     if @meta.get("mode") == "gui"
+      handcodedCriterion = new XLF.HandCodedSkipLogicCriterion(@serialize())
       @reset()
       @meta.set("mode", "handcode")
-      @add(new XLF.HandCodedSkipLogicCriterion())
+      @add(handcodedCriterion)
     else
-      @reset()
-      @meta.set("mode", "gui")
+      serialized = @serialize()
+      parseHelper.parseSkipLogic(@, serialized, @parentRow)
+      if @parseable == false
+        @add(new XLF.HandCodedSkipLogicCriterion(serialized))
+        alert("Could not parse: invalid / unsupported criteria")
+      else
+        @meta.set("mode", "gui")
+        @each((item) -> item.linkUp())
+

 # To be extended ontop of a RowDetail when the key matches
@@ -555,27 +563,30 @@
     @skipLogicCollection.serialize()

   parse: ()->
-    value = @get('value')
-    @skipLogicCollection.meta.set("rawValue", value)
+    parseHelper.parseSkipLogic(@skipLogicCollection, @get('value'), @parentRow)
+
+  linkUp: ->
+    @skipLogicCollection.each (i)-> i.linkUp()
+
+parseHelper =
+  parseSkipLogic: (collection, value, parentRow) ->
+    collection.meta.set("rawValue", value)
     try
       parsedValues = XLF.skipLogicParser(value)
-      @skipLogicCollection.empty()
-      @skipLogicCollection.parseable = true
+      collection.reset()
+      collection.parseable = true
       for crit in parsedValues.criteria
-        @skipLogicCollection.add({
+        collection.add({
           name: crit.name
-          parentRow: @parentRow
+          parentRow: parentRow
           expressionCode: crit.operator
           criterion: crit.response_value
         }, silent: true)
       if parsedValues.operator
-        @skipLogicCollection.meta.set("delimSelect", parsedValues.operator.toLowerCase())
+        collection.meta.set("delimSelect", parsedValues.operator.toLowerCase())
       ``
     catch e
-      @skipLogicCollection.parseable = false
-
-  linkUp: ->
-    @skipLogicCollection.each (i)-> i.linkUp()
+      collection.parseable = false

 @XLF.RowDetailMixins =
   relevant: SkipLogicDetailMixin


Less moment reparsing

Less moment reparsing

--- resources/assets/coffee/react/_components/beatmapset-mapping.coffee
+++ resources/assets/coffee/react/_components/beatmapset-mapping.coffee
@@ -22,41 +22,42 @@
 bn = 'beatmapset-mapping'
 dateFormat = 'LL'

-@BeatmapsetMapping = ({user, beatmapset}) ->
-  user ?= beatmapset.user
+class @BeatmapsetMapping extends React.PureComponent
+  render: =>
+    user = @props.user ? @props.beatmapset.user

-  div className: bn,
-    div
-      className: 'avatar avatar--beatmapset'
-      style:
-        backgroundImage: "url(#{user.avatar_url})"
+    div className: bn,
+      div
+        className: 'avatar avatar--beatmapset'
+        style:
+          backgroundImage: "url(#{user.avatar_url})"

-    div className: "#{bn}__content",
-      div className: "#{bn}__mapper",
-        osu.trans 'beatmapsets.show.details.made-by'
-        a
-          className: "#{bn}__user"
-          href: laroute.route 'users.show', user: user.id
-          user.username
+      div className: "#{bn}__content",
+        div className: "#{bn}__mapper",
+          osu.trans 'beatmapsets.show.details.made-by'
+          a
+            className: "#{bn}__user"
+            href: laroute.route 'users.show', user: user.id
+            user.username

-      div null,
-        osu.trans 'beatmapsets.show.details.submitted'
-        time
-          className: "#{bn}__date timeago"
-          datetime: beatmapset.submitted_date
-          moment(beatmapset.submitted_date).format dateFormat
-
-      if beatmapset.ranked > 0
         div null,
-          osu.trans "beatmapsets.show.details.#{beatmapset.status}"
+          osu.trans 'beatmapsets.show.details.submitted'
           time
             className: "#{bn}__date timeago"
-            datetime: beatmapset.submitted_date
-            moment(beatmapset.ranked_date).format dateFormat
-      else
-        div null,
-          osu.trans 'beatmapsets.show.details.updated'
-          time
-            className: "#{bn}__date timeago"
-            datetime: beatmapset.submitted_date
-            moment(beatmapset.last_updated).format dateFormat
+            datetime: @props.beatmapset.submitted_date
+            moment(@props.beatmapset.submitted_date).format dateFormat
+
+        if @props.beatmapset.ranked > 0
+          div null,
+            osu.trans "beatmapsets.show.details.#{@props.beatmapset.status}"
+            time
+              className: "#{bn}__date timeago"
+              datetime: @props.beatmapset.submitted_date
+              moment(@props.beatmapset.ranked_date).format dateFormat
+        else
+          div null,
+            osu.trans 'beatmapsets.show.details.updated'
+            time
+              className: "#{bn}__date timeago"
+              datetime: @props.beatmapset.submitted_date
+              moment(@props.beatmapset.last_updated).format dateFormat


support displaying output files using .url parameter from clsi

--- services/web/public/coffee/ide/pdf/controllers/PdfController.coffee
+++ services/web/public/coffee/ide/pdf/controllers/PdfController.coffee
@@ -76,12 +76,15 @@
 				# prepare query string
 				qs = {}
 				# define the base url. if the pdf file has a build number, pass it to the clsi in the url
-				if fileByPath['output.pdf']?.build?
+				if fileByPath['output.pdf']?.url?
+					$scope.pdf.url = fileByPath['output.pdf'].url
+				else if fileByPath['output.pdf']?.build?
 					build = fileByPath['output.pdf'].build
 					$scope.pdf.url = "/project/#{$scope.project_id}/build/#{build}/output/output.pdf"
-					# no need to bust cache, build id is unique
 				else
 					$scope.pdf.url = "/project/#{$scope.project_id}/output/output.pdf"
+				# check if we need to bust cache (build id is unique so don't need it in that case)
+				if not fileByPath['output.pdf']?.build?
 					qs = { cache_bust : "#{Date.now()}" }
 				# add a query string parameter for the compile group
 				if response.compileGroup?
@@ -111,7 +114,9 @@
 		fetchLogs = (logFile, blgFile) ->

 			getFile = (name, file) ->
-				if file?.build?
+				if file.url?  # FIXME clean this up when we have file.urls out consistently
+					url = file.url
+				else if file?.build?
 					url = "/project/#{$scope.project_id}/build/#{file.build}/output/#{name}"
 				else
 					url = "/project/#{$scope.project_id}/output/#{name}"


catch exception when debugging

catch exception when debugging

--- client.iced
+++ client.iced
@@ -78,10 +78,14 @@
   stats.requireLogin = !lixian.logon
   if e
     e.extra = execute_log.map ({prefix, data})->
-      data = JSON.stringify data, null, ' ' unless data?.constructor is String
-      data.split('\n').map (line)->
-        "#{prefix}> #{line}\n"
-      .join('')
+      try
+        message = JSON.stringify data, null, ' ' unless data?.constructor is String
+        return (message||'undefined').split('\n').map (line)->
+          "#{prefix}> #{line}\n"
+        .join('')
+      catch e
+        console.error e.message
+        return String data
     .join('-------------\n')
     if e.message.match /重新登录/i
       stats.requireLogin = true


Bug fix: wasn't passing third parameter to BytecodeSpool.

--- coffee/DebuggerController.coffee
+++ coffee/DebuggerController.coffee
@@ -102,7 +102,7 @@
     if @spool
       # run step until the first position
       @spool.queueRunUntil 'NEXT_POSITION'
-      bytecode = @spool.getNextBytecode false, null
+      bytecode = @spool.getNextBytecode false, Opal.nil, Opal.nil
       @highlighter.interpret bytecode
     @render()


Don't use Code Mirror for Mobile Safari; just use textarea.

--- coffee/ExerciseController.coffee
+++ coffee/ExerciseController.coffee
@@ -34,9 +34,15 @@
         autofocus: true
         readOnly: false
       textarea = @$div.querySelector('textarea.code')
-      codeMirror = CodeMirror.fromTextArea textarea, options
-      makeRetriever = (codeMirror) -> (-> codeMirror.getValue())
-      @retrieveNewCode = makeRetriever codeMirror
+      isMobileSafari = ->
+         navigator.userAgent.match(/(iPod|iPhone|iPad)/) &&
+         navigator.userAgent.match(/AppleWebKit/)
+      if isMobileSafari()
+        @retrieveNewCode = -> textarea.value
+      else
+        codeMirror = CodeMirror.fromTextArea textarea, options
+        makeRetriever = (codeMirror) -> (-> codeMirror.getValue())
+        @retrieveNewCode = makeRetriever codeMirror
       if @color == 'red' || @color == 'green'
         @handleRun()
     @render callback


close the modal if modal-button is clicked

--- coffee/jquery.contactform.coffee
+++ coffee/jquery.contactform.coffee
@@ -74,7 +74,7 @@
                 </div>
               </div>
               <div class="modal-footer">
-                <button type="button" class="btn btn-primary" id="btn_send_contactform">#{config.button_text}</button>
+                <button type="button" class="btn btn-primary" id="btn_send_contactform" data-dismiss="modal">#{config.button_text}</button>
               </div>
             </div>
           </div>


Small fixes

Small fixes

Signed-off-by: Alessandro Vioni <257254d42c5a52e6dce5913d1e55698f549f60b0@gmail.com>

--- coffeescript/jq-validator.coffee
+++ coffeescript/jq-validator.coffee
@@ -4,13 +4,13 @@
   jqValidator: (options) ->
     # Default settings
     settings =
-      debug: false
-      isForm: false
-      buttonClass: ".btn"
-      personalized_error: false
-      correct_function: () ->
-      error_function: () ->
-      callback: () ->
+      debug: false # debug option for console.log
+      isForm: false # False for form not wrapper in <form></form> (.net), True for correct form wrapping
+      buttonClass: ".btn" # The class of the submit button
+      personalized_error: false # true for give personalized callback functions in case of wrong or correct form compilation
+      correct_function: () -> # function called if true personalized_error when a field is compiled correctly
+      error_function: () -> # function called when the form is not compiled in the right way
+      callback: () -> # callback called when the form is submitted

     # Merge default settings with options.
     settings = $.extend settings, options
@@ -37,7 +37,7 @@

       # Put the regular expressions
       # in a function that is used
-      # later with data-object HTML5
+      # later with data- attributes
       checkIsMail = (input) ->
         pattern = new RegExp(emailReg)
         return pattern.test(input)
@@ -122,7 +122,7 @@
         # Debug
         log "value is #{value}"

-        # Now check the whole lot of data-object
+        # Now check the whole lot of data- attribute
         # and if the field is compiled as requested
         if value > issuedLength and ismail is true and isname is true and isnumber is true
           $(element).addClass "checked"


Save 3 bytes

Save 3 bytes

--- src/objects/remotes.coffee
+++ src/objects/remotes.coffee
@@ -28,7 +28,7 @@
   true

 msi = (line)->
-  ~line.files.join('!').indexOf '-msi'
+  ~line.files.join().indexOf '-msi'

 # Latest version available to upgrade to
 exports.v = ->


JApp: Update schema and create method based on new schema

--- workers/social/lib/social/models/app.coffee
+++ workers/social/lib/social/models/app.coffee
@@ -59,7 +59,6 @@

     schema              :

-      authorNick        : String
       identifier        :
         type            : String
         set             : (value)-> value?.trim()
@@ -68,10 +67,17 @@
         type            : String
         set             : (value)-> value?.trim()
         required        : yes
-      url               :
+      urls              :
+        script          :
+          type          : String
+          set           : (value)-> value?.trim()
+          required      : yes
+        style           :
+          type          : String
+          set           : (value)-> value?.trim()
+      repourl           :
         type            : String
         set             : (value)-> value?.trim()
-        required        : yes
       counts            :
         followers       :
           type          : Number
@@ -101,6 +107,11 @@
         type            : String
         default         : (value)-> Inflector.slugify @name.toLowerCase()
       slug_             : String
+
+      originId          :
+        type            : ObjectId
+        required        : yes
+

     relationships       :
       creator           :
@@ -124,6 +135,8 @@

   @getAuthorType =-> JAccount

+  capitalize = (str)-> str.charAt(0).toUpperCase() + str.slice(1)
+
   @create = permit 'create apps',

     success: (client, data, callback)->
@@ -133,27 +146,30 @@
       {connection:{delegate}} = client
       {profile} = delegate

-      if not data.name or not data.url
+      if not data.name or not data.urls?.script
         return callback new KodingError 'Name and Url is required!'

+      data.name = capitalize @slugify data.name
+
       data.manifest           ?= {}

-      # Overwrite the user information in manifest
+      # Overwrite the user/app information in manifest
+      data.manifest.name       = data.name
       data.manifest.authorNick = profile.nickname
       data.manifest.author     = "#{profile.firstName} #{profile.lastName}"

       # Optionals
       data.manifest.version   ?= "1.0"
-      data.identifier         ?= "com.koding.apps.#{data.name}"
+      data.identifier         ?= "com.koding.apps.#{data.name.toLowerCase()}"

       app           = new JApp
-        url         : data.url
         name        : data.name
+        urls        : data.urls
         type        : data.type or 'web-app'
         manifest    : data.manifest
         identifier  : data.identifier
         version     : data.manifest.version
-        authorNick  : data.manifest.authorNick
+        originId    : delegate.getId()

       app.save (err)->
         return callback err  if err


Closes #1705; Fix ungroup of messages after join message

--- packages/rocketchat-ui-message/message/message.coffee
+++ packages/rocketchat-ui-message/message/message.coffee
@@ -128,7 +128,7 @@
 			else
 				$currentNode.removeClass('new-day')

-			if previousDataset.groupable is 'false'
+			if previousDataset.groupable is 'false' or currentDataset.groupable is 'false'
 				$currentNode.removeClass('sequential')
 			else
 				if previousDataset.username isnt currentDataset.username or parseInt(currentDataset.timestamp) - parseInt(previousDataset.timestamp) > RocketChat.settings.get('Message_GroupingPeriod') * 1000


Increase minimum_chrome_version to 32 in order to support Promise and onWheel

--- _resources/manifest.cson
+++ _resources/manifest.cson
@@ -1,7 +1,7 @@
 name: 'Popup my Bookmarks'
 version: ''
 manifest_version: 2
-minimum_chrome_version: '26'
+minimum_chrome_version: '32'

 default_locale: 'en'
 description: '__MSG_extDesc__'


Better spec for circular:paused

Better spec for circular:paused


Remove comments

Remove comments

--- src/main/menu.coffee
+++ src/main/menu.coffee
@@ -108,40 +108,3 @@
       label: 'Help'
       submenu: []
     ]
-
-    # [
-    #   label: 'Electron'
-    #   submenu: [
-    #     label: 'Quit'
-    #     accelerator: 'Command+Q'
-    #     click: -> mediator.emit 'quit-requested'
-    #   ,
-    #     label: 'Hide Electron'
-    #     accelerator: 'Command+H'
-    #     selector: 'hide:'
-    #   ]
-    # ,
-    #   label: 'File'
-    #   submenu: [
-    #     label: 'Open'
-    #     accelerator: 'Command+O'
-    #     click: -> mediator.emit 'open-file-requested'
-    #   ]
-    # ,
-    #   label: 'Display'
-    #   submenu: [
-    #     label: 'Open Dev Tools'
-    #     accelerator: 'Command+Alt+I'
-    #     click: -> mediator.emit 'open-dev-tools-requested'
-    #   ]
-    # ,
-    #   label: 'Window'
-    #   submenu: [
-    #     label: 'Open New Window'
-    #     accelerator: 'Command+N'
-    #     click: -> mediator.emit 'open-new-window-requested'
-    #   ]
-    # ,
-    #   label: 'Help'
-    #   submenu: []
-    # ]


Remove defaults for up.Record

Remove defaults for up.Record

--- lib/assets/javascripts/unpoly/classes/record.coffee
+++ lib/assets/javascripts/unpoly/classes/record.coffee
@@ -5,12 +5,8 @@
   fields: ->
     throw 'Return an array of property names'

-  defaults: ->
-    {}
-
   constructor: (options) ->
-    attributes = u.merge(@defaults(), @attributes(options))
-    u.assign(this, attributes)
+    u.assign(this, @attributes(options))

   attributes: (source = @) =>
     u.only(source, @fields()...)


Fix bug with Foundation undefined

Fix bug with Foundation undefined

--- app/assets/javascripts/application.js.coffee
+++ app/assets/javascripts/application.js.coffee
@@ -1,5 +1,7 @@
+#= require vendor/modernizr
 #= require jquery
 #= require jquery_ujs
+#= require foundation/foundation
 #= require foundation
 #= require rails.validations
 #= require rails.validations.simple_form
@@ -13,5 +15,5 @@
 #= require tools/nprogress

 $(document).on 'ready page:load page:restore', ->
-  $(document).foundation()
+  $(document).foundation
   $('.autosize').autosize()


You can use the enter key to login

You can use the enter key to login

--- app/assets/javascripts/application.js.coffee
+++ app/assets/javascripts/application.js.coffee
@@ -543,7 +543,7 @@
       $(this).closest(".question-body").find(".error").remove()

   # Disable using enter key to submit on the form
-  $("form").on 'keypress', (e) ->
+  $("form .steps-progress-content").on 'keypress', (e) ->
     if e.keyCode == 13
       target = $(e.target)
       if !target.is("textarea") && !target.is(":button,:submit")


Scroll to first unread comment on discussion page load

--- app/assets/javascripts/discussions.js.coffee
+++ app/assets/javascripts/discussions.js.coffee
@@ -7,6 +7,11 @@

 $ ->
   if $("body.discussions.show").length > 0
+
+    if $('#js-dog-ear').length > 0
+      $('html,body').animate
+        scrollTop: $('#js-dog-ear').offset().top - 75
+
     autocomplete_path = $('#comment-input').data('autocomplete-path')
     $("textarea").atwho
       at: '@'


trigger folioLazyLoadLoaded on loaded image

--- app/assets/javascripts/folio/lazyload.coffee
+++ app/assets/javascripts/folio/lazyload.coffee
@@ -17,7 +17,10 @@
         el.style.visibility = ''
         el.alt = el.dataset.alt if el.dataset.alt
         el.classList.remove(selector.replace('.', ''))
-        $(el).closest('.f-image').addClass('f-image--loaded')
+        $(el)
+          .trigger('folioLazyLoadLoaded')
+          .closest('.f-image')
+          .addClass('f-image--loaded')

     window.folioLazyloadInstances.push(
       new LazyLoad $.extend({}, defaults, options)


second modal is displayed in top of the first one

--- app/assets/javascripts/form/dialog.js.coffee
+++ app/assets/javascripts/form/dialog.js.coffee
@@ -182,4 +182,8 @@
           frame.html request.responseText
           return

+  $(document).on 'dialog:show', '.dialog', ->
+    if $('.modal').length
+      $(this).parent().css('z-index', '1050')
+
 ) ekylibre, jQuery


fix position statement character counter

--- app/assets/javascripts/validations.js.coffee
+++ app/assets/javascripts/validations.js.coffee
@@ -16,7 +16,8 @@
     $(".error-message").hide()
     max = 250 if $(this).hasClass('limit-250')
     max = 150 if $(this).hasClass('limit-150')
-    chars = $(this).val().length
+    string = $(this).val().replace(/\r\n|\r|\n/g, '11')
+    chars = string.length
     left = max - chars
     display_count(left, $(this))

@@ -30,19 +31,19 @@
 ### FUNCTIONS ###

 Application.validateForm = (form) ->
-  formValid = true
+  formError = ''
   form.find(".validate-presence").each((index, field) ->
-    formValid = false unless validatePresence(field)
+    formError = 'A mandatory field has been left blank, please fill it out and try again' unless validatePresence(field)
     return
     )
   form.find(".validate-length").each((index, field) ->
-    formValid = false unless validateInputLength(field)
+    formError = 'One of the fields has too many characters, please remove some of the characters and try again' unless validateInputLength(field)
     return
     )
-  formValid = false unless Application.validateEmailsAndConfirm($(".validate-emails"))
-  formValid = false unless Application.validateMotionCloseDate($(".motion-closing-inputs"))
-  alert('There is a problem with the form') unless formValid
-  formValid
+  formError = 'There is a problem with the form' unless Application.validateEmailsAndConfirm($(".validate-emails"))
+  formError = 'The date you have chosen is invalid, please choose another date and try again' unless Application.validateMotionCloseDate($(".motion-closing-inputs"))
+  alert(formError) unless formError == ''
+  (formError == '')

 Application.hideAllErrorMessages = () ->
   $(".inline-help").hide()


HUD size

HUD size

--- src/engine_hud.coffee
+++ src/engine_hud.coffee
@@ -11,7 +11,7 @@
 @param {Object} self Reference to the engine
 ###
 Engine.HUD = (I, self) ->
-  hudCanvas = $("<canvas width=640 height=480 />").powerCanvas()
+  hudCanvas = $("<canvas width=#{App.width} height=#{App.height} />").powerCanvas()
   hudCanvas.font("bold 9pt consolas, 'Courier New', 'andale mono', 'lucida console', monospace")

   self.bind "draw", (canvas) ->


changes colons to smeicolons in survey task help text

--- app/classifier/tasks/survey/editor-help.cjsx
+++ app/classifier/tasks/survey/editor-help.cjsx
@@ -113,7 +113,7 @@
           The question to ask
           Can the user select multiple answers? Y or N
           Is an answer required? Y or N
-          The answers, ,-separated
+          The answers separated with ;
           Choices to ask this question for (leave blank for all by default).
           Choices to not ask this question for.

@@ -121,7 +121,7 @@
           Is this an example question?
           Y
           N
-          Yes, No, Maybe
+          Yes; No; Maybe

           Dolphin


Fix center step reset

Fix center step reset

--- app/controllers/classify-steps/center.coffee
+++ app/controllers/classify-steps/center.coffee
@@ -30,7 +30,7 @@

   reset: ->
     super
-    @svg.el.removeChild @circle.el
+    @circle?.remove()
     @circle = null

   onMouseDownSubject: (e) ->


bugfix--better handle subjects with less than 4 images

--- app/controllers/classifier.coffee
+++ app/controllers/classifier.coffee
@@ -146,7 +146,7 @@
           @nextFrame.hide()
         @enableMarkingSurfaces()
         @currSighting = new Sighting({
-          type:"asteroid",
+          type:"asteroid",
           inverted: @invert
         })
         @el.find('.asteroid-classifier').show()
@@ -526,7 +526,8 @@
     @togglePausePlayIcons()

   startPlayingFrames: (startingFrame) ->
-    startingFrame %= @markingSurfaceList.length
+    startingFrame %= @surfacesContainer.find(".marking-surface:has(.frame-image)").length
+
     @activateFrame startingFrame
     @playTimeout = setTimeout (=> @startPlayingFrames startingFrame + 1), 500

@@ -599,7 +600,7 @@
           surface.addShape 'ellipse', class: "known-asteroid", opacity: 0.75, cx: x, cy: y, rx: radius, ry: radius, fill: "none", stroke: "rgb(20,200,20)", 'stroke-width': 2
         @evaluateAnnotations(P_ref)
     @el.attr 'flicker', 'true'
-    @surfacesContainer.children().clone().appendTo(@summaryImageContainer)
+    @surfacesContainer.find(".marking-surface:has(.frame-image)").clone().appendTo(@summaryImageContainer)
     element.hide() for element in [@surfacesContainer, @playButton, @frameSlider, @finishButton, @rightPanel.find('.answers'), @cycleButton]
     @startPlayingFrames(0)
     @populateSummary()


display correct light curve using selected_light_curve

--- app/controllers/classifier.coffee
+++ app/controllers/classifier.coffee
@@ -107,8 +107,8 @@

     # TODO: use Subject data to choose the right lightcurve
-    jsonFile = @subject.location['14-1'] # read actual subject
-
+    jsonFile = @subject.selected_light_curve.location
+
     # DEBUG CODE
     # jsonFile = './offline/subject.json' # for debug only
     console.log 'json_file: ', jsonFile


fix: propagate the promisification to the redisobserver

--- src/observers/redis.observer.coffee
+++ src/observers/redis.observer.coffee
@@ -15,10 +15,16 @@
       @redis.auth redis.auth if redis.auth

     publish: (notification, value) =>
-      @redis.publishAsync @_getChannel(notification), @_buildValue_(value)
+      Promise.props
+        channel: @_getChannel(notification)
+        value: @_buildValue_(value)
+      .then ({ channel, value }) => @redis.publishAsync channel, value

     _getChannel: (notification) =>
-      "#{@_channelPrefix_(notification.type)}/#{@_messagePath_ notification}"
+      Promise.props
+        channelPrefix: @_channelPrefix_ notification.type
+        messagePath: @_messagePath_ notification
+      .then ({ channelPrefix, messagePath }) => "#{channelPrefix}/#{messagePath}"

     _messagePath_: => throw new Error "not supported `_messagePath_`"
     _buildValue_: => throw new Error "not supported `_buildValue_`"


require start_gmond to only allow a single interval

--- src/jolokiasrv.coffee
+++ src/jolokiasrv.coffee
@@ -110,6 +110,7 @@
   ###
   start_gmond: (host, port, spoof) =>
     return unless @interval
+    if @gmond_interval_id then stop_gmond()
     @gmond_interval_id = setInterval () =>
       @submit_metrics()
     , @interval
@@ -118,7 +119,9 @@
    * Stops the gmond metric spooler.
   ###
   stop_gmond: () =>
-    if @gmond_interval_id then clearInterval(@gmond_interval_id)
+    if @gmond_interval_id
+      clearInterval(@gmond_interval_id)
+      @gmond_interval_id = null

   ###*
    * Submits gmetric data to the gmond target.


add timeRangeView directly to controlsView.

--- coffee/src/shells/highlights_shell.coffee
+++ coffee/src/shells/highlights_shell.coffee
@@ -317,7 +317,7 @@

     @remixMediaView.controlsView.$el.append @controlsTemplate()

-    @$('.time-controls').prepend @timeRangeView.render().el
+    @remixMediaView.controlsView.$el.prepend @timeRangeView.render().el
     @timeRangeView.$el.hide()

     # @inactivateHighlights()


ShellEditor: bugfix, index -> indexOf

ShellEditor: bugfix, index -> indexOf

--- coffee/src/views/shell_editor_view.coffee
+++ coffee/src/views/shell_editor_view.coffee
@@ -124,7 +124,7 @@
     @

   removeShell: (shell) =>
-    index = @model.shells().index shell
+    index = @model.shells().indexOf shell
     @model.shells().remove shell

     [view] = @remixerViews.splice(index, 1)


Add pattern-matching for ".me" package's .sh macro

--- grammars/roff.cson
+++ grammars/roff.cson
@@ -880,6 +880,17 @@
 				1: {name: "punctuation.definition.macro.roff"}
 				2: {name: "entity.function.name.roff"}
 			patterns: [include: "#underline-first"]
+		},{
+
+			# .sh: Section heading
+			name: "markup.heading.section.function.me.macro.roff"
+			begin: "^([.'])[ \t]*(sh)[ \t]+((?!\")\\S+)\\b[ \t]*(?!$|\\n|\\\\\")"
+			end:   "(?<![^\\\\]\\\\|^\\\\)(?=$|\\n)|(?=\\\\\")"
+			beginCaptures:
+				1: {name: "punctuation.definition.macro.roff"}
+				2: {name: "entity.function.name.roff"}
+				3: {name: "variable.parameter.roff", patterns: [include: "#params"]}
+			patterns: [include: "#bold-first"]
 		}]


Avoid catching comments inside generic macro names

Comments weren't highlighted if they immediately followed a macro's name
without any preceding whitespace. E.g., something like ".macro\"Comment"
was matched like one entity.

--- grammars/roff.cson
+++ grammars/roff.cson
@@ -1161,7 +1161,7 @@

 			# Generic macro highlighting
 			name: "meta.function.macro.roff"
-			begin: "^([.'])[ \t]*(\\S+)"
+			begin: "^([.'])[ \t]*((?:[^\\s\\\\]|\\\\(?![#\"]).)+)"
 			end:   "(?<!\\\\)(?=$)|(?=\\\\\")"
 			beginCaptures:
 				1: {name: "punctuation.definition.macro.roff"}


We know the titles now

We know the titles now

--- components/CalculateState.coffee
+++ components/CalculateState.coffee
@@ -43,17 +43,17 @@
     # We have a previous state to compare with

     if newState.team isnt previous.team
-      console.log "Portal #{newState.guid} switched from #{previous.team} to #{newState.team}"
+      console.log "Portal #{newState.title} switched from #{previous.team} to #{newState.team}"
       state.state = 'ownerchange'
       return state

     if newState.level > previous.level and newState.level isnt 8
-      console.log "Portal #{newState.guid} (#{newState.team}) upgraded"
+      console.log "Portal #{newState.title} (#{newState.team}) upgraded"
       state.state = 'upgraded'
       return state

     if newState.level < previous.level or newState.health < previous.health
-      console.log "Portal #{newState.guid} (#{newState.team}) under attack"
+      console.log "Portal #{newState.title} (#{newState.team}) under attack"
       state.state = 'attack'
       return state

@@ -66,12 +66,12 @@
       return state

     if newState.level is 8 and newState.team is 'NEUTRAL'
-      console.log "Portal #{newState.guid} (#{newState.team}) has disco"
+      console.log "Portal #{newState.title} (#{newState.team}) has disco"
       state.state = 'disco'
       return state

     if newState.level > 8
-      console.log "Portal #{newState.guid} (#{newState.team}) plays calvinball"
+      console.log "Portal #{newState.title} (#{newState.team}) plays calvinball"
       state.state = 'calvinball'
       return state


End groups immediately when sending action

--- components/DispatchAction.coffee
+++ components/DispatchAction.coffee
@@ -34,11 +34,9 @@
       sentToIdx = handler
     sentTo.beginGroup grp, sentToIdx for grp in groups
     sentTo.send data, sentToIdx
+    sentTo.endGroup sentToIdx for grp in groups
   c.inPorts.in.on 'endgroup', (group) ->
     grp = groups.pop()
-    return unless sentTo
-    sentTo.endGroup sentToIdx
-    return if groups.length
   c.inPorts.in.on 'disconnect', ->
     return unless sentTo
     sentTo.disconnect sentToIdx


Added resortTabHandles method to sort handles when a handle index has changed.

--- client/Framework/components/tabs/KDTabView.coffee
+++ client/Framework/components/tabs/KDTabView.coffee
@@ -65,10 +65,24 @@

       newTabHandle.$().css maxWidth: @getOptions().maxHandleWidth

+      newTabHandle.on "HandleIndexHasChanged", @bound "resortTabHandles"
+
       return paneInstance
     else
       warn "You can't add #{paneInstance.constructor.name if paneInstance?.constructor?.name?} as a pane, use KDTabPaneView instead."
       false
+
+  resortTabHandles:->
+    #   {subViews}       = @holder
+    #   temp             = subViews[@draggedItemIndex]
+
+    #   subViews.splice(@draggedItemIndex, 1);
+    #   targetPos = if @isDraggingToLeft then Math.abs @passedHandleLength - @draggedItemIndex else @passedHandleLength + @draggedItemIndex
+    #   subViews.splice(targetPos, 0, temp);
+
+    #   names = ''
+    #   names += ', ' + $(subView.domElement).find('b').text() for subView in subViews
+    #   log names

   removePane:(pane)->
     pane.emit "KDTabPaneDestroy"


avoid unnecessary dynamic require

avoid unnecessary dynamic require

--- src/lib/facade.coffee
+++ src/lib/facade.coffee
@@ -401,7 +401,7 @@
             @nonExistingClassNames[modFullName] = true

             modFullName = fullName # strip module name
-            klass = @getModule().requireOwn(fullName)
+            klass = @classes[fullName] or @getModule().requireOwn(fullName)

         if not klass?
             @nonExistingClassNames[fullName] = true


on-screen kbd: renamed Alt->APL and added tooltip

--- web-src/index.coffee
+++ web-src/index.coffee
@@ -104,6 +104,7 @@
     ['⋄', 'Statement separator']
     ['⍺', 'Left formal parameter']
     ['⍵', 'Right formal parameter']
+    ['APL', 'Press backquote (`) followed by another key to insert an APL symbol, e.g. `r inserts rho (⍴)']
   ]

   # Symbols setup {{{1
@@ -136,7 +137,7 @@
     display:
       bksp: 'Bksp'
       shift: '⇧'
-      alt: 'Alt'
+      alt: 'APL'
       enter: 'Enter'
       exec: '⍎'
     autoAccept: true


'testResolved()' option added

'testResolved()' option added

--- process.coffee
+++ process.coffee
@@ -13,8 +13,9 @@
 		val

 index = pathUtils.resolve fs.realpathSync('.'), opts.path
-{platform, test, path} = opts
+{platform, test, testResolved, path} = opts
 test ?= -> true
+testResolved ?= -> true

 customGlobalProps = Object.create null
 mockGlobal = (obj) ->
@@ -67,6 +68,7 @@
 		return r

 	if pathUtils.isAbsolute(req) and req isnt index
+		filename = req
 		modulePath = req
 		parentPath = ''
 	else
@@ -74,13 +76,14 @@
 		modulePath = pathUtils.relative base, filename
 		parentPath = pathUtils.relative base, parent.id

-	unless modulesByPaths[modulePath]
-		modules.push modulePath
-		modulesByPaths[modulePath] = true
+	if req is index or testResolved(req, filename, modulePath, parentPath)
+		unless modulesByPaths[modulePath]
+			modules.push modulePath
+			modulesByPaths[modulePath] = true

-	if parentPath
-		mpaths = paths[parentPath] ?= {}
-		mpaths[req] = modulePath
+		if parentPath
+			mpaths = paths[parentPath] ?= {}
+			mpaths[req] = modulePath

 	r


Retina community friends

Retina community friends

--- gulp/images.coffee
+++ gulp/images.coffee
@@ -12,8 +12,8 @@
       gm
         .background 'transparent'
         .gravity 'Center'
-        .resize 80, 60
-        .extent 80, 60
+        .resize 160, 120
+        .extent 160, 120
         .transparent 'white'
         .setFormat 'png'
     .pipe community.restore()


Atom: Never change with of tabs.

Atom: Never change with of tabs.

--- files/.atom/config.cson
+++ files/.atom/config.cson
@@ -48,3 +48,5 @@
   "theme-switcher":
     syntaxThemeOne: "one-dark-syntax"
     syntaxThemeTwo: "one-light-syntax"
+  "one-dark-ui":
+    tabSizing: "Even"


optargs now work

optargs now work

--- lib/bebopt.coffee
+++ lib/bebopt.coffee
@@ -75,12 +75,12 @@
       if fn is undefined
         self._parent.type ?= op
         self["_#{listName}"][name] = self._parent
-        self["_#{listName}"][name].parent = self._parent
+        #self["_#{listName}"][name].parent = self._parent
       else
         self["_#{listName}"][name] =
           cb: fn
           type: op
-          parent: self._parent
+          #parent: self._parent
       self._parent = self["_#{listName}"][name]
       return self))

@@ -103,35 +103,6 @@
       _arg: arg
     }

-# validates the incoming command-line options
-# e.g. if an option that takes a required arg
-# has no arg, then it causes bebopt to exit
-# non-zero and prints an error message.
-  _optTypeError: (opt, list) =>
-    # XXX this function MUST be called after we've already
-    # taken the optargs passed in the `--OPTION ARG' fashion
-    # and smooshed them in their respective @_{short,long,half} list
-    # objects.
-    if opt.type is 'arg'
-      if opt.optarg is undefined
-        err = "#{@app}: "
-        if list[1] is 'short'
-          err += "option requires an argument -- '#{optName}'"
-        else
-          err += "option '--#{optName}' requires an argument"
-        console.error(err)
-        process.exit(1)
-      else
-        list[0][opt.arg].optarg = opt.optarg
-    else if opt.type is 'flag'
-      if opt.optarg isnt undefined
-        err = "#{@app}"
-        err += "option '--#{optName}' doesn't allow an argument"
-        console.error(err)
-        process.exit(1)
-      else
-        @[list.name][optName].arg = true
-
   # loops through the option lists making sure that any options that
   # if an option takes an arg
   # but that arg was specified in the
@@ -141,22 +112,20 @@
   # that arg is added to the option's object
   # within the respective
   # option list, such as `@_long'
-  _catchSpaceDelimArgs: (opt, list, listName) =>
-    @_catchInvalidOpt(opt, listName)
+  _catchSpaceDelimArgs: (opt, list) =>
     if list[opt.arg].type isnt 'flag'
       if opt.optarg is undefined
         @_args.forEach((nonOpt, ind) =>
           if nonOpt.index is (opt.index + 1)
             opt.optarg = nonOpt.arg
             delete @_args[ind])
-        return opt
+    return opt

   _catchInvalidOpt: (opt, list) =>
-    console.log list.toString()
-    if opt.arg.length < 2 and @_short[opt.arg] is undefined
+    if list is 'short' and @_short[opt.arg] is undefined
       console.error("#{@app}: invalid option -- '#{opt.arg}'")
       process.exit(1)
-    else if opt.arg.length > 1 and @_long[opt.arg] is undefined
+    else if list is 'long' and @_long[opt.arg] is undefined
       console.error("#{@app}: unrecognized option '--#{opt.arg}'")
       process.exit(1)

@@ -184,8 +153,10 @@
           console.log _elem
           @_opts.push(_elem))

-  _takesArg: (opt) =>
-    if opt.arg.length < 2
+  _takesArg: (opt, listName) =>
+    @_catchInvalidOpt(opt, listName)
+    if listName is 'short'
+      type = @_short[opt.arg].type
       if type isnt 'flag'
         return true
       else
@@ -206,11 +177,42 @@
       dashes = elem.arg.replace(/^(--?).*/, '$1').length # number of dashes
       elem.arg = elem.arg.replace(/^--?(.*)/, '$1')
       if dashes is 2
-        elem = @_catchSpaceDelimArgs(elem, @_long, 'long')
-      else
-        elem = @_catchSpaceDelimArgs(elem, @_short, 'short')
+        if @_takesArg(elem, 'long')
+          elem = @_catchSpaceDelimArgs(elem, @_long)
+        console.log elem
+        @_bindOptToList(elem, 'long')
+      else
+        if @_takesArg(elem, 'short')
+          elem = @_catchSpaceDelimArgs(elem, @_short)
+        console.log elem
+        @_bindOptToList(elem, 'short')

     )
+
+  _bindOptToList: (opt, listName) =>
+    list = @["_#{listName}"]
+    type = list[opt.arg].type
+    if type is 'arg'
+      if opt.optarg is undefined
+        err = "#{@app}: "
+        if listName is 'short'
+          err += "option requires an argument -- '#{opt.arg}'"
+        else
+          err += "option '--#{opt.arg}' requires an argument"
+        console.error(err)
+        process.exit(1)
+      else
+        list[opt.arg].optarg = opt.optarg
+    else if type is 'optarg'
+      list[opt.arg].optarg = opt.optarg
+    else if type is 'flag'
+      if opt.optarg isnt undefined
+        err = "#{@app}"
+        err += "option '--#{opt.arg}' doesn't allow an argument"
+        console.error(err)
+        process.exit(1)
+      else
+        list[opt.arg].optarg = true

 # processes `process.arv' producing two arrays, one containing options,
 # their indexes and any arguments passed in the `--OPTION=ARG' fashion,
@@ -252,5 +254,6 @@
   parse: () =>
     @_gather()
     @_resolveOpts()
+    @_log(@)

 module.exports = Bebopt


uploads are sequential

uploads are sequential

required so that we can enforce maximum files user can upload

--- lib/assets/javascripts/attachinary.js.coffee
+++ lib/assets/javascripts/attachinary.js.coffee
@@ -53,6 +53,7 @@
         paramName: 'file'
         headers: {"X-Requested-With": "XMLHttpRequest"}
         dropZone: @$input
+        sequentialUploads: true

       if @$input.attr('accept')
         options.acceptFileTypes = new RegExp("^#{@$input.attr('accept').split(",").join("|")}$", "i")


obsolete options

obsolete options

--- lib/assets/javascripts/attachinary.js.coffee
+++ lib/assets/javascripts/attachinary.js.coffee
@@ -4,9 +4,6 @@
     index: 0
     config:
       disableWith: 'Uploading...'
-      filesClass: 'attachinary_files'
-      removeLabel: 'remove'
-      removeClass: ''
       invalidFormatMessage: 'Invalid file format'
       template: """
         <ul>


Document browser support in up.browser module docs

--- lib/assets/javascripts/unpoly/browser.coffee
+++ lib/assets/javascripts/unpoly/browser.coffee
@@ -1,9 +1,18 @@
 ###*
-Browser interface
-=================
-
-Some browser-interfacing methods and switches that
-we can't currently get rid off.
+Browser support
+===============
+
+Unpoly supports all modern browsers. It degrades gracefully with old versions of Internet Explorer:
+
+IE 10, IE11, Edge
+: Full support
+
+IE 9
+: Page updates that change browser history fall back to a classic page load.
+
+IE 8
+: Unpoly prevents itself from booting itself, leaving you with a classic server-side application.
+

 @class up.browser
 ###


Add check and fix for season 9.5

Add check and fix for season 9.5

--- app/index.coffee
+++ app/index.coffee
@@ -63,7 +63,12 @@
     sortedSeasons = for season, {_id: id, total, complete} of project.seasons
       total ?= 0
       complete ?= 0
-      name = if season is '0' then 'Lost Season' else "Season #{ season }"
+      name = if season is '0'
+        'Lost Season'
+      else if season is '999'
+        'Season 9.5'
+      else
+        "Season #{ season }"
       {season, id, name, total, complete}

     sortedSeasons.sort (a, b) ->


默认 app_name 获取方法太麻烦，用yoeman 自带方法重构

默认 app_name 获取方法太麻烦，用yoeman 自带方法重构

--- app/index.coffee
+++ app/index.coffee
@@ -32,10 +32,7 @@
         type: 'input'
         name: 'appName'
         message: 'What is your app name'
-        default: ->
-          rootArr = root.split '/'
-          len = rootArr.length
-          rootArr[len - 1]
+        default: @appname
       ]
       @prompt prompts, ((props) ->
         @config.set props


Added the ability to get the facts fro a specific host from foreman

--- src/scripts/foreman.coffee
+++ src/scripts/foreman.coffee
@@ -30,9 +30,24 @@
       .get({'strictSSL': false}) (err, res, body) ->
         msg.send(body)

+  host = (msg, host) ->
+    msg.send(path)
+    msg.http("#{url}/#{host}/facts?format=json")
+      .headers
+        'Authorization': auth
+      .get({'strictSSL': false}) (err, res, body) ->
+        msg.send(body)
+
   robot.respond /foreman (hosts|environments|users|facts})/i, (msg) ->
     if msg.message.user.id is robot.name
       return

     hosts msg, msg.match[1], (text) ->
+      msg.send(text)
+
+  robot.respond /foreman (.*) facts/i, (msg) ->
+    if msg.message.user.id is robot.name
+      return
+
+    host msg, msg.match[1], (text) ->
       msg.send(text)


Added 3 new gifs

Added 3 new gifs


Adding missing "@" for "parent" at ln. 274. Adding existential postfix on nearby "@parent"s.

--- framer/Context.coffee
+++ framer/Context.coffee
@@ -257,12 +257,12 @@

 	@define "width",
 		get: ->
-			return @parent.width if @parent
+			return @parent.width if @parent?
 			return window.innerWidth

 	@define "height",
 		get: ->
-			return @parent.height if @parent
+			return @parent.height if @parent?
 			return window.innerHeight

 	@define "frame", get: -> {x:0, y:0, width:@width, height:@height}
@@ -270,7 +270,7 @@
 	@define "point", get: -> _.pick(@frame, ["x", "y"])
 	@define "canvasFrame",
 		get: ->
-			return @frame if not parent
+			return @frame if not @parent?
 			return @parent.canvasFrame

 	@define "backgroundColor",


typo error in markdown plugin renderer

typo error in markdown plugin renderer

--- lib/plugins/renderers/markdown.plugin.coffee
+++ lib/plugins/renderers/markdown.plugin.coffee
@@ -12,7 +12,7 @@

 	# Render some content
 	render: ({inExtension,outExtension,templateData,file}, next) ->
-		tru
+		try
 			if inExtension in ['md','markdown'] and outExtension is 'html'
 				file.content = markdown.parse file.content
 				next()
@@ -22,4 +22,4 @@
 			return next(err)

 # Export Plugin
-module.exports = MarkdownPlugin+module.exports = MarkdownPlugin


Updates jenkins.coffee to respond correctly to latest jenkins update and adds convenience method to build.

Jenkins changed the http status code for their REST API in the last version. These changes handle responses better when requesting a build. It also adds a convenient way to start build, by keeping an internal array of jobs, so that the user can request a build by sending a number instead of a job's name.

--- src/scripts/jenkins.coffee
+++ src/scripts/jenkins.coffee
@@ -9,6 +9,7 @@
 #   HUBOT_JENKINS_AUTH
 #
 # Commands:
+#   hubot jenkins b <jobNumber> - builds the job specified by jobNumber. List jobs to get number.
 #   hubot jenkins build <job> - builds the specified Jenkins job
 #   hubot jenkins build <job>, <params> - builds the specified Jenkins job with parameters as key=value&key2=value2
 #   hubot jenkins list <filter> - lists Jenkins jobs
@@ -19,6 +20,21 @@
 #   dougcole

 querystring = require 'querystring'
+
+# Holds a list of jobs, so we can trigger them with a number
+# instead of the job's name. Gets populated on when calling
+# list.
+jobList = []
+
+jenkinsBuildById = (msg) ->
+  # Switch the index with the job name
+  job = jobList[parseInt(msg.match[1]) - 1]
+
+  if job
+    msg.match[1] = job
+    jenkinsBuild(msg)
+  else
+    msg.reply "I couldn't find that job. Try `jenkins list` to get a list."

 jenkinsBuild = (msg) ->
     url = process.env.HUBOT_JENKINS_URL
@@ -36,11 +52,11 @@
     req.header('Content-Length', 0)
     req.post() (err, res, body) ->
         if err
-          msg.send "Jenkins says: #{err}"
-        else if res.statusCode == 302
-          msg.send "Build started for #{job} #{res.headers.location}"
+          msg.reply "Jenkins says: #{err}"
+        else if 200 <= res.statusCode < 400 # Or, not an error code.
+          msg.reply "(#{res.statusCode}) Build started for #{job} #{url}/job/#{job}"
         else
-          msg.send "Jenkins says: #{body}"
+          msg.reply "Jenkins says: Status #{res.statusCode} #{body}"

 jenkinsDescribe = (msg) ->
     url = process.env.HUBOT_JENKINS_URL
@@ -63,6 +79,7 @@
           try
             content = JSON.parse(body)
             response += "JOB: #{content.displayName}\n"
+            response += "URL: #{url}/job/#{job}\n"

             if content.description
               response += "DESCRIPTION: #{content.description}\n"
@@ -137,21 +154,30 @@
           try
             content = JSON.parse(body)
             for job in content.jobs
+              # Add the job to the jobList
+              index = jobList.indexOf(job.name)
+              if index == -1
+                jobList.push(job.name)
+                index = jobList.indexOf(job.name)
+
               state = if job.color == "red" then "FAIL" else "PASS"
               if filter.test job.name
-                response += "#{state} #{job.name}\n"
+                response += "[#{index + 1}] #{state} #{job.name}\n"
             msg.send response
           catch error
             msg.send error

 module.exports = (robot) ->
-  robot.respond /jenkins build ([\w\.\-_ ]+)(, (.+))?/i, (msg) ->
+  robot.respond /j(?:enkins)? build ([\w\.\-_ ]+)(, (.+))?/i, (msg) ->
     jenkinsBuild(msg)

-  robot.respond /jenkins list( (.+))?/i, (msg) ->
+  robot.respond /j(?:enkins)? b (\d+)/i, (msg) ->
+    jenkinsBuildById(msg)
+
+  robot.respond /j(?:enkins)? list( (.+))?/i, (msg) ->
     jenkinsList(msg)

-  robot.respond /jenkins describe (.*)/i, (msg) ->
+  robot.respond /j(?:enkins)? describe (.*)/i, (msg) ->
     jenkinsDescribe(msg)

   robot.jenkins = {


Be octospy.coffee can understand 'commit_comment' of github

--- src/scripts/octospy.coffee
+++ src/scripts/octospy.coffee
@@ -123,6 +123,11 @@
     """
       {{sender.login}} commented on issue {{issue.number}} on {{repo_name}} "{{{overflow issue.title 25}}}" {{issue.html_url}}
       > {{{overflow comment.body 120}}}
+    """
+  commit_comment:
+    """
+    {{sender.login}} commented on commit {{comment.commit_id}} on {{repo_name}} {{comment.html_url}}
+    > {{{overflow comment.body 120}}}
     """
   pull_request: (context) ->
     template = switch context.action