FIXES #383: Numbers that start with . not recognized

--- lexer.coffee
+++ lexer.coffee
@@ -106,8 +106,12 @@
   # Matches numbers, including decimals, hex, and exponential notation.
   number_token: ->
     return false unless number: @match NUMBER, 1
+    @i: + number.length
+    if starts number, (leading: '.') + leading
+      while starts number, leading
+        @token leading, leading
+        number: number.substring leading.length
     @token 'NUMBER', number
-    @i: + number.length
     true

   # Matches strings, including multi-line strings. Ensures that quotation marks
@@ -481,7 +485,7 @@

 # Token matching regexes.
 IDENTIFIER    : /^([a-zA-Z\$_](\w|\$)*)/
-NUMBER        : /^(\b((0(x|X)[0-9a-fA-F]+)|([0-9]+(\.[0-9]+)?(e[+\-]?[0-9]+)?)))\b/i
+NUMBER        : /^(((\b0(x|X)[0-9a-fA-F]+)|(((\b[0-9]+(\.[0-9]+)?)|([\.]+[0-9]+))(e[+\-]?[0-9]+)?)))\b/i
 HEREDOC       : /^("{6}|'{6}|"{3}\n?([\s\S]*?)\n?([ \t]*)"{3}|'{3}\n?([\s\S]*?)\n?([ \t]*)'{3})/
 INTERPOLATION : /^\$([a-zA-Z_@]\w*(\.\w+)*)/
 OPERATOR      : /^([+\*&|\/\-%=<>:!?]+)([ \t]*)/


Add spark to list of potential index types in console

--- src/client/factories/iUtils.coffee
+++ src/client/factories/iUtils.coffee
@@ -2,7 +2,7 @@

 module.exports = ->
   parseTaskId: (taskId) ->
-    m = taskId.match /^((hadoop_convert_segment)|index_(hadoop|realtime)|(archive))_(.+)_(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z)/
+    m = taskId.match /^((hadoop_convert_segment)|index_(hadoop|realtime|spark)|(archive))_(.+)_(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z)/
     type = m[3]
     type ||= m[2]
     throw Error("Can't parse #{taskId}") unless m


clean up query -- only an issue in prod :-/

--- apps/search/routes.coffee
+++ apps/search/routes.coffee
@@ -2,6 +2,8 @@

 @index = (req, res) ->
   term    = req.query.q
+  # Clean up query.q. Sometimes is returned as term?q=term
+  term    = term.split('?q=')[0]
   search  = new Search
   search.fetch
     data: { term: term }


updated "double-print when inline editing in collection view" bug

--- client/collection_view.coffee
+++ client/collection_view.coffee
@@ -94,7 +94,7 @@
     $this.find('input').select()
     $this.find('input').on 'blur', ->
       updated_val = $this.find('input').val()
-      $this.html updated_val
+      $this.html ""
       $this.addClass('houston-collection-field')
       id = $this[0].parentNode.dataset.id
       field_name = $this.data('field')


Use named simulationType and Complexity values

--- src/code/stores/graph-store.coffee
+++ src/code/stores/graph-store.coffee
@@ -535,7 +535,7 @@
   #   1 (static)         if there are no collectors
   #   2 (time)           if there are collectors
   getMinimumSimulationType: ->
-    minSimulationType = 0
+    minSimulationType = AppSettingsStore.store.SimulationType.diagramOnly

     links = @getLinks()
     for link in links
@@ -543,10 +543,10 @@

       if source.isAccumulator or target.isAccumulator
         # we know we have to be time-based
-        return 2
+        return AppSettingsStore.store.SimulationType.time
       else if link.relation?.formula
         # we have a defined relationship, so we know we'll be at least 1
-        minSimulationType = 1
+        minSimulationType = AppSettingsStore.store.SimulationType.static

     return minSimulationType

@@ -560,12 +560,11 @@
       continue unless (source = link.sourceNode) and (target = link.targetNode)

       if link.relation?.formula
-        # we know we'll be at least 1 or 2
         relation = RelationFactory.selectionsFromRelation(link.relation)
         if relation.scalar.id isnt "aboutTheSame"
-          return 1
-
-    return 0
+          return AppSettingsStore.store.Complexity.expanded
+
+    return AppSettingsStore.store.Complexity.basic

   loadData: (data) ->
     log.info "json success"


change watchField

change watchField

--- src/coffee/directives/field.coffee
+++ src/coffee/directives/field.coffee
@@ -11,7 +11,7 @@

     l = (scope) ->
       scope.error = false
-      scope.service.watchFieldValidation(scope.field.name,
+      scope.service.watchField(scope.field.name,
         () -> scope.error = false,
         (e) -> scope.error = e)


Make unread count in title sum of all unread

Fixes bug with issue #21

--- client/startup/startup.coffee
+++ client/startup/startup.coffee
@@ -80,18 +80,7 @@
 		removed: (data) ->
 			KonchatNotification.removeRoomNotification(data.rid)

-	updateUnread = (unreadCount) ->
-		rxFavico.set 'type', 'warn'
-		rxFavico.set 'count', unreadCount
-		if unreadCount > 0
-			document.title = '(' + unreadCount + ') Rocket.Chat'
-		else
-			document.title = 'Rocket.Chat'
-
 	ChatSubscription.find({}, { fields: { unread: 1 } }).observeChanges
-		added: (id, fields) ->
-			updateUnread(fields.unread)
-
 		changed: (id, fields) ->
 			if fields.unread and fields.unread > 0

@@ -100,7 +89,18 @@

 				KonchatNotification.newMessage()

-			updateUnread(fields.unread)
+	Tracker.autorun ->
+		unreadCount = 0
+		subscriptions = ChatSubscription.find({}, { fields: { unread: 1 } })
+		(unreadCount += r.unread) for r in subscriptions.fetch()
+
+		rxFavico.set 'type', 'warn'
+		rxFavico.set 'count', unreadCount
+
+		if unreadCount > 0
+			document.title = '(' + unreadCount + ') Rocket.Chat'
+		else
+			document.title = 'Rocket.Chat'

 	# Add ascii support to emojione
 	emojione?.ascii = true


peter's only

peter's only

--- src/configuration-generator.coffee
+++ src/configuration-generator.coffee
@@ -89,8 +89,10 @@
           config = @_legacyConversion _.cloneDeep config # prevent accidental mutation

           getSetConfig = @_mutilateGetSetNodes uuid: flowData.flowId, token: flowToken, config
-          oauthConfig = @_ohThatOauth userData, config
-          config = _.defaultsDeep {}, config, oauthConfig, getSetConfig
+
+          template = _.cloneDeep config
+          channelApiMatch = @channelConfig.get template.type
+          config = _.defaultsDeep {channelApiMatch, template}, config, getSetConfig

           flowConfig[instanceId] = {config: config, data: data}

@@ -278,60 +280,6 @@

     return config

-  _ohThatOauth: (userData, template) =>
-    userApiMatch = _.findWhere(userData.api, type: template.type)
-    return {} unless userApiMatch?
-
-    channelApiMatch = @channelConfig.get template.type
-    return {} unless channelApiMatch?
-
-    channelConfig = _.pick channelApiMatch,
-      'bodyFormat'
-      'followAllRedirects'
-      'skipVerifySSL'
-      'hiddenParams'
-      'auth_header_key'
-      'bodyParams'
-
-    config = _.defaults {}, template, channelConfig
-
-    if userApiMatch.token_crypt
-      userApiMatch.token  = textCrypt.decrypt userApiMatch.token_crypt
-    if userApiMatch.secret_crypt
-      userApiMatch.secret = textCrypt.decrypt userApiMatch.secret_crypt
-    if userApiMatch.refreshToken_crypt
-      userApiMatch.refreshToken = textCrypt.decrypt userApiMatch.refreshToken_crypt
-
-    config.apikey = userApiMatch.apikey
-
-    userToken = userApiMatch.token ? userApiMatch.key
-
-    userOAuth =
-      access_token: userToken
-      access_token_secret: userApiMatch.secret
-      refreshToken: userApiMatch.refreshToken
-      expiresOn: userApiMatch.expiresOn
-      defaultParams: userApiMatch.defaultParams
-
-    config.defaultParams = userApiMatch.defaultParams
-
-    channelOauth =  channelApiMatch.oauth?[process.env.NODE_ENV]
-    channelOauth ?= channelApiMatch.oauth
-    channelOauth ?= {tokenMethod: channelApiMatch.auth_strategy}
-
-    config.oauth = _.defaults {}, userOAuth, template.oauth, channelOauth
-
-    if channelApiMatch.overrides
-      config.headerParams = _.extend {}, template.headerParams, channelApiMatch.overrides.headerParams
-
-    config.oauth.key ?= config.oauth.clientID
-    config.oauth.key ?= config.oauth.consumerKey
-
-    config.oauth.secret ?= config.oauth.clientSecret
-    config.oauth.secret ?= config.oauth.consumerSecret
-
-    return JSON.parse JSON.stringify config # removes things that are undefined
-
   _generateFlowMetricId: =>
     NodeUuid.v4()


Don't capture results if a section returns an error.

--- src/prompt.coffee
+++ src/prompt.coffee
@@ -62,7 +62,7 @@
       (done) =>
         async.each @_orderedSections, (section, complete) =>
           @_content section, (err, content) ->
-            section._formattedContent = content
+            section._formattedContent = content unless err
             complete()
         , done

@@ -136,4 +136,4 @@

 # Expose `Prompt`.
-exports = module.exports = Prompt+exports = module.exports = Prompt


doc'ing core_object

doc'ing core_object

--- src/core_object.coffee
+++ src/core_object.coffee
@@ -16,6 +16,29 @@
     ###*
     External access to instance variables. Use of this property should be avoided
     in general, but can come in handy from time to time.
+
+

+
+    myObject = Core
+      r: 255
+      g: 0
+      b: 100
+
+    # a bad idea most of the time, but it's
+    # pretty convenient to have available.
+    myObject.I.r
+
+    => 255
+
+    myObject.I.g
+
+    => 0
+
+    myObject.I.b
+
+    => 100
+
+

     @name I
     @fieldOf Core#


One more place where we should check for JSON parsing errors

--- src/remote.coffee
+++ src/remote.coffee
@@ -89,8 +89,11 @@
         res.setEncoding "utf8"
         res.on "data", (chunk) -> data.push chunk.toString()
         res.on "end", ->
-          data = JSON.parse(data.join("")).messages
-          defer.resolve data
+          try
+            data = JSON.parse(data.join("")).messages
+            defer.resolve data
+          catch e
+            defer.reject e
       req.on "error", defer.reject
       req.write input
       req.end()


Always call the binder's update function if present when updating models on a view instance. [#183]

--- src/rivets.coffee
+++ src/rivets.coffee
@@ -150,11 +150,10 @@
         else
           @view.config.adapter.subscribe @model, @keypath, @sync
           @sync() if @view.config.preloadData
-
-        @binder.update?.call @, models
     else
       @sync()
-      @binder.update?.call @, models
+
+    @binder.update?.call @, models

 # Rivets.View
 # -----------


[minor] indent

[minor] indent

--- src/runner.coffee
+++ src/runner.coffee
@@ -19,8 +19,7 @@
   program.help() unless args[0]?

   if args[0] is 'init'
-    initializer = new Initializer
-      global: program.global
+    initializer = new Initializer global: program.global
     initializer.run()
   else
     config = new Config program.config


add base property for controller_wrapper

--- src/sirius.coffee
+++ src/sirius.coffee
@@ -1,12 +1,12 @@
 ###!
-#  Sirius.js v0.2.1
+#  Sirius.js v0.3.0
 #  (c) 2014 fntzr
 #  license: MIT
 ###

 #
 # @author fntzr <fantazuor@gmal.com>
-# @version 0.2.1
+# @version 0.3.0
 # @mixin
 # A main module, which included methods and classes for work with application.
 Sirius =
@@ -370,6 +370,13 @@
   start : false

   ###
+    a shared methods for controllers
+  ###
+  controller_wrapper: {
+    redirect: Sirius.redirect
+  } #fixme add redirect and then merge with user
+
+  ###
     when, false, then hash will be add into last for url, for true, no
     false:
       "http://example.com/" - start
@@ -407,6 +414,10 @@
     @route   = options["route"]   || @route
     @logger  = options["logger"]  || @logger
     @start   = options["start"]   || @start
+
+    @controller_wrapper = for key, value in (options["controller_wrapper"] || {})
+                            @controller_wrapper[key] = options["controller_wrapper"][key]
+
     @hash_always_on_top = options["hash_always_on_top"] || @hash_always_on_top
     @use_hash_routing_for_old_browsers = options["use_hash_routing_for_old_browsers"] || @use_hash_routing_for_old_browsers
     @logger("Logger enabled? #{@log}")


Fix coordinate icon abbreviation.

Fix coordinate icon abbreviation.

--- coffeescripts/cards-es.coffee
+++ coffeescripts/cards-es.coffee
@@ -1908,7 +1908,7 @@
             text: '''%IMPERIALONLY%%LINEBREAK%Después de que realices una acción de interferencia contra una nave enemiga, puedes elegir una nave que esté situada a alcance 1 de esa nave enemiga y no esté interferida, y asignarle 1 ficha de Interferencia.'''
         'Tactical Officer':
             name: "Oficial táctico"
-            text: '''%IMPERIALONLY%%LINEBREAK%Tu barra de acciones gana el icono COORDINATE.'''
+            text: '''%IMPERIALONLY%%LINEBREAK%Tu barra de acciones gana el icono %COORDINATE%.'''
         'Saw Gerrera':
             text: '''%REBELONLY%%LINEBREAK%Cuando ataques, puedes sufrir 1 de daño para cambiar todos tus resultados %FOCUS% por resultados %CRIT%.'''
         'Director Krennic':


Light Scyk title missing

Light Scyk title missing

-Added "ship" tag on it, as it wasn't appearing in the builder.

I hope it will be the last of these small fixes for a while :P

--- coffeescripts/cards-es.coffee
+++ coffeescripts/cards-es.coffee
@@ -1895,6 +1895,7 @@
         '''Merchant One''':
             text: '''C-ROC Cruiser only.%LINEBREAK%Your upgrade bar 1 additional %CREW% upgrade icon and 1 additional %TEAM% upgrade icon and loses 1 %CARGO% upgrade icon.'''
         '''"Light Scyk" Interceptor''':
+            ship: "Interceptor M3-A"
             text: '''M3-A Interceptor only.%LINEBREAK%All Damage cards dealt to you are dealt faceup.  You may treat all bank maneuvers (%BANKLEFT% or %BANKRIGHT%) as green maneuvers.  You cannot equip Modification upgrades.'''
         '''Insatiable Worrt''':
             text: '''After you perform the recover action, gain 3 energy.'''


respond

respond

--- src/scripts/hubot-variables.coffee
+++ src/scripts/hubot-variables.coffee
@@ -53,7 +53,7 @@
   # constructor
   robot.brain.data.variables = {}

-  robot.respond /^create var (\w+)$/, (msg) ->
+  robot.respond /create var (\w+)$/, (msg) ->
     varname = msg.match[1]
     if robot.brain.data.variables[varname]
       msg.reply "Sorry, Variable of '" + varname + "' already exists."
@@ -63,7 +63,7 @@
     }
     msg.reply "Okay."

-  robot.respond /^remove var (\w+)\s*(!+)?$/, (msg) ->
+  robot.respond /remove var (\w+)\s*(!+)?$/, (msg) ->
     varname = msg.match[1]
     is_forced = !!msg.match[2]
     if !robot.brain.data.variables[varname]
@@ -78,7 +78,7 @@
       msg.reply "Okay, removed variable " + varname
     delete robot.brain.data.variables[varname]

-  robot.respond /^add value (\w+) (.*)$/, (msg) ->
+  robot.respond /add value (\w+) (.*)$/, (msg) ->
     varname = msg.match[1]
     value = msg.match[2]
     lcvalue = value.toLowerCase()
@@ -99,7 +99,7 @@
     robot.brain.data.variables[varname].values.push value
     msg.reply "Okay."

-  robot.respond /^remove value (\w+) (.*)$/, (msg) ->
+  robot.respond /remove value (\w+) (.*)$/, (msg) ->
     varname = msg.match[1]
     value = msg.match[2]
     if !robot.brain.data.variables[varname]
@@ -111,7 +111,7 @@
     robot.brain.data.variables[varname].values.remove value
     msg.reply "Okay."

-  robot.respond /^var (\w+) type (var|verb|noun)$/, (msg) ->
+  robot.respond /var (\w+) type (var|verb|noun)$/, (msg) ->
     varname = msg.match[1]
     if !robot.brain.data.variables[varname]
       msg.reply "Sorry, I don't know of a variable '" + varname + "'."
@@ -119,14 +119,14 @@
     robot.brain.data.variables[varname].type = msg.match[2]
     msg.reply "Okay."

-  robot.respond /^list var (\w+)$/, (msg) ->
+  robot.respond /list var (\w+)$/, (msg) ->
     varname = msg.match[1]
     if !robot.brain.data.variables[varname]
       msg.reply "Sorry, I don't know of a variable '" + varname + "'."
       return
     msg.reply robot.brain.data.variables[varname].values.join(', ')

-  robot.respond /^list vars$/, (msg) ->
+  robot.respond /list vars$/, (msg) ->
     ret = []
     for varname in Object.keys(robot.brain.data.variables)
       v = robot.brain.data.variables[varname]


made team/board selection work for both our and my

--- scripts/awesome.team.coffee
+++ scripts/awesome.team.coffee
@@ -30,7 +30,7 @@
     .post(data) (err, res, body) ->
       msg.send "Team Added!"

-  robot.hear /make (.*) our team/i, (msg) ->
+  robot.hear /make (.*) (my|our) team/i, (msg) ->
     exists = false
     if robot.brain.team is undefined
       robot.brain.team = {}
@@ -71,7 +71,7 @@
     .post(data) (err, res, body) ->
       msg.send "Board Added!"

-  robot.hear /make (.*) our board/i, (msg) ->
+  robot.hear /make (.*) (my|our) board/i, (msg) ->
     if robot.brain.team is undefined
       msg.reply "You haven't picked a team yet!"
       exit


BW: Combine two kinds of move extensions.

--- server/bw/data/moves.coffee
+++ server/bw/data/moves.coffee
@@ -38,16 +38,10 @@
   extendMove name, ->
     @afterSuccessfulHit = (battle, user, target) ->
       if target.has(Klass)
-        # TODO: Placeholder
         @fail(battle)
         return

-      target.attach(Klass, options)
-
-extendWithPrimaryStatus = (name, status) ->
-  extendMove name, ->
-    @afterSuccessfulHit = (battle, user, target) ->
-      target.attach(status, source: user)
+      target.attach(Klass, source: user)

 # Extends a move in the move list as an attack with a secondary effect.
 #
@@ -646,7 +640,7 @@
 extendWithSecondaryBoost 'Crunch', 'target', .2, defense: -1
 extendWithSecondaryBoost 'Crush Claw', 'target', .5, defense: -1
 extendWithSecondaryEffect 'Dark Pulse', .2, Attachment.Flinch
-extendWithPrimaryStatus 'Dark Void', Status.Sleep
+extendWithPrimaryEffect 'Dark Void', Status.Sleep
 makeBoostMove 'Defend Order', 'self', defense: 1, specialDefense: 1
 makeBoostMove 'Defense Curl', 'self', defense: 1

@@ -764,7 +758,7 @@
 makeRechargeMove 'Giga Impact'
 extendWithSecondaryBoost 'Glaciate', 'target', 1, speed: -1
 makeWeightBased 'Grass Knot'
-extendWithPrimaryStatus 'GrassWhistle', Status.Sleep
+extendWithPrimaryEffect 'GrassWhistle', Status.Sleep
 makeBoostMove 'Growl', 'target', attack: -1
 makeBoostMove 'Growth', 'self', attack: 1, specialAttack: 1

@@ -792,7 +786,7 @@
 makeBoostMove 'Howl', 'self', attack: 1
 makeRechargeMove 'Hydro Cannon'
 makeRechargeMove 'Hyper Beam'
-extendWithPrimaryStatus 'Hypnosis', Status.Sleep
+extendWithPrimaryEffect 'Hypnosis', Status.Sleep
 makeMomentumMove 'Ice Ball'
 extendWithSecondaryBoost 'Icy Wind', 'target', 1, speed: -1
 makeBoostMove 'Iron Defense', 'self', defense: 2
@@ -840,7 +834,7 @@
 makeLockOnMove 'Lock-On'
 makeWeightBased 'Low Kick'
 extendWithSecondaryBoost 'Low Sweep', 'target', 1, speed: -1
-extendWithPrimaryStatus 'Lovely Kiss', Status.Sleep
+extendWithPrimaryEffect 'Lovely Kiss', Status.Sleep

 extendMove 'Lucky Chant', ->
   @execute = (battle, user, opponents) ->
@@ -895,9 +889,9 @@
 }
 extendWithBoost 'Overheat', 'self', specialAttack: -2
 extendWithSecondaryStatus 'Poison Fang', .3, Status.Toxic
-extendWithPrimaryStatus 'Poison Gas', Status.Poison
+extendWithPrimaryEffect 'Poison Gas', Status.Poison
 extendWithSecondaryStatus 'Poison Jab', .3, Status.Poison
-extendWithPrimaryStatus 'PoisonPowder', Status.Poison
+extendWithPrimaryEffect 'PoisonPowder', Status.Poison
 extendWithSecondaryStatus 'Poison Sting', .3, Status.Poison
 extendWithSecondaryStatus 'Poison Tail', .1, Status.Poison
 extendWithSecondaryStatus 'Powder Snow', .1, Status.Freeze
@@ -955,11 +949,11 @@
 extendWithSecondaryBoost 'Silver Wind', 'self', .1, {
   attack: 1, defense: 1, speed: 1, specialAttack: 1, specialDefense: 1
 }
-extendWithPrimaryStatus 'Sing', Status.Sleep
+extendWithPrimaryEffect 'Sing', Status.Sleep
 makeBoostMove 'Skull Bash', 'self', defense: 1
 extendWithSecondaryEffect 'Sky Attack', .3, Attachment.Flinch
 makeRecoveryMove 'Slack Off'
-extendWithPrimaryStatus 'Sleep Powder', Status.Sleep
+extendWithPrimaryEffect 'Sleep Powder', Status.Sleep
 extendWithSecondaryStatus 'Sludge', .3, Status.Poison
 extendWithSecondaryStatus 'Sludge Bomb', .3, Status.Poison
 extendWithSecondaryStatus 'Sludge Wave', .1, Status.Poison
@@ -993,7 +987,7 @@
     user.unattach(Attachment.Stockpile)
     user.boost(defense: num, specialDefense: num)

-extendWithPrimaryStatus 'Spore', Status.Sleep
+extendWithPrimaryEffect 'Spore', Status.Sleep
 extendWithSecondaryEffect 'Steamroller', .3, Attachment.Flinch
 makeStompMove 'Steamroller'
 extendWithSecondaryBoost 'Steel Wing', 'self', .1, defense: 1
@@ -1010,7 +1004,7 @@
 makeBasePowerBoostMove 'Stored Power', 20, 860, 'user'
 makeBoostMove 'String Shot', 'target', speed: -1
 extendWithSecondaryBoost 'Struggle Bug', 'target', 1, specialAttack: -1
-extendWithPrimaryStatus 'Stun Spore', Status.Paralyze
+extendWithPrimaryEffect 'Stun Spore', Status.Paralyze
 makeWeatherMove 'Sunny Day', Weather.SUN
 extendWithBoost 'Superpower', 'self', attack: -1, defense: -1
 extendWithPrimaryEffect 'Supersonic', Attachment.Confusion
@@ -1063,14 +1057,14 @@
     return @accuracy

 extendWithFangEffect 'Thunder Fang', .1, Status.Paralyze
-extendWithPrimaryStatus 'Thunder Wave', Status.Paralyze
+extendWithPrimaryEffect 'Thunder Wave', Status.Paralyze
 extendMove 'Thunder Wave', ->
   @ignoresImmunities = -> false
 extendWithSecondaryStatus 'Thunderbolt', .1, Status.Paralyze
 extendWithSecondaryStatus 'ThunderPunch', .1, Status.Paralyze
 extendWithSecondaryStatus 'ThunderShock', .1, Status.Paralyze
 makeBoostMove 'Tickle', 'target', attack: -1, defense: -1
-extendWithPrimaryStatus 'Toxic', Status.Toxic
+extendWithPrimaryEffect 'Toxic', Status.Toxic
 extendMove 'Tri Attack', ->
   oldFunc = @afterSuccessfulHit
   @afterSuccessfulHit = (battle, user, target, damage) ->
@@ -1090,7 +1084,7 @@
 makeEruptionMove 'Water Spout'
 extendWithSecondaryEffect 'Waterfall', .2, Attachment.Flinch
 makeRandomSwitchMove "Whirlwind"
-extendWithPrimaryStatus 'Will-O-Wisp', Status.Burn
+extendWithPrimaryEffect 'Will-O-Wisp', Status.Burn
 makeBoostMove 'Withdraw', 'user', defense: 1
 makeBoostMove 'Work Up', 'user', attack: 1, specialAttack: 1
 makeTrappingMove "Wrap"


Make the non-existing range also work on Firefox.

--- frontend/script.coffee
+++ frontend/script.coffee
@@ -164,6 +164,6 @@
     slider.attr("value", player.player.volume * slider.attr("max"))
     $(".volume-intensity").text(Math.round(100 * player.player.volume))
     slider.change ->
-        newVolume = @value / (@max - @min)
-        if newVolume != NaN
-                player.player.volume = newVolume
+        me = $(@)
+        newVolume = me.val() / (me.attr("max") - me.attr("min"))
+        player.player.volume = newVolume


JNewApp:checkData : fix the usage of unless, damn.

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

   checkData = (data, profile)->
-    unless data.name or data.urls?.script or data.manifest
+    if not data.name or not data.url or not data.manifest
       return new KodingError 'Name, Url and Manifest is required!'
     unless typeof(data.manifest) is 'object'
       return new KodingError 'Manifest should be an object!'


restore window layout when connecting

restore window layout when connecting

--- a.coffee
+++ a.coffee
@@ -166,6 +166,7 @@
     """

   toInterpreter '<?xml version="1.0" encoding="utf-8"?><Command><cmd>Connect</cmd><id>0</id><args><Connect><Token /></Connect></args></Command>'
+  toInterpreter '<?xml version="1.0" encoding="utf-8"?><Command><cmd>GetWindowLayout</cmd><id>0</id><args><GetWindowLayout /></args></Command>'

   client.on 'end', -> log 'interpreter disconnected'; socket.emit 'end'
   socket.on 'disconnect', -> log 'browser disconnected'


Adding `lodash` dependency in `app_skel`, closes #57.

--- cli/templates/config/polvo.coffee
+++ cli/templates/config/polvo.coffee
@@ -55,6 +55,7 @@
       'history': "#{theoricus}/vendors/history.js"
       'inflection': "#{theoricus}/vendors/inflection.js"
       'jquery': "#{theoricus}/vendors/jquery.js"
+      'lodash': "#{theoricus}/vendors/lodash.js"

       # app
       # 'app_vendor_name': 'vendors/app_vendor_name.js'


Tweak the filter text

Tweak the filter text

--- workflows/filter-habitats.coffee
+++ workflows/filter-habitats.coffee
@@ -21,8 +21,8 @@
     label: "Dunes and dune slacks"
   },{
     value: "quarries"
-    label: "Man-made (quarries, railway lines, old industrial sites etc)"
+    label: "Human-made (quarries, railway lines, old industrial sites etc)"
   },{
     value: "verges"
-    label: "Man-made (road verges, churchyards etc)"
+    label: "Human-made (road verges, churchyards etc)"
   }]


Fixes to Clients model

Fixes to Clients model

- Renamed clientId to client to eliminate confusion between client.id,
client.clientId, authCode.clientId, etc
- Removed clientName
- Added tonaccount to dummy data
- afterFind, check if !client

--- src/server/database/Clients.coffee
+++ src/server/database/Clients.coffee
@@ -12,16 +12,10 @@
 		type: Seq.UUID
 		defaultValue: Seq.UUIDV4
 		primaryKey: true
-	clientId:
+	client:
 		type: Seq.STRING
 		allowNull: false
 		unique: true
-		validate:
-			isAlphanumeric: true
-			notEmpty: true
-	clientName:
-		type: Seq.STRING
-		allowNull: false
 		validate:
 			isAlphanumeric: true
 			notEmpty: true
@@ -36,6 +30,7 @@
 	redirectUri:
 		type: Seq.STRING
 		allowNull: false
+		unique: true
 		validate:
 			isUrl: true
 			notEmpty: true,
@@ -46,10 +41,14 @@
 			return encrypted is fromDatabase
 		_dummy: ->
 			Clients.create
-				clientId: "bestapp"
-				clientName: "BestApp"
-				clientSecret: "secret"
-				redirectUri: "localhost"
+					client: "postman"
+					clientSecret: "secret"
+					redirectUri: "https://www.getpostman.com/oauth2/callback"
+				.then () ->
+					Clients.create
+						client: "tonaccount"
+						clientSecret: "secret"
+						redirectUri: "http://localhost:5001/api/callback"
 	hooks:
 		beforeCreate: (client, options) ->
 			unencrypted = client.clientSecret
@@ -65,6 +64,7 @@
 				client.clientSecret = encrypted
 				client.salt = salt
 		afterFind: (client, options) ->
+			if !client then return client
 			clientSecret = client.clientSecret
 			salt = client.salt
 			unencrypted = crypto.unaes clientSecret, salt, secret


Localize error message when on fetch (#447)

Fixes #444

--- client/app/views/konnector.coffee
+++ client/app/views/konnector.coffee
@@ -409,5 +409,5 @@
         errorMessage = model.get 'importErrorMessage'

         @model.set 'importErrorMessage', errorMessage
-        @showErrors errorMessage
-
+        @showErrors t errorMessage
+


Update Checkins.coffee

Update Checkins.coffee

--- client/components/Checkins.coffee
+++ client/components/Checkins.coffee
@@ -66,6 +66,9 @@
             Не забудьте с собой паспорт (если паспорта еще нет, то свидетельство о рождении) -- его могут спросить охранники на входе!
             </p>

+            <Alert bsStyle="danger">
+                30 декабря и 6 января занятий не будет. Ниже запись на 13 января.
+            </Alert>
             {
             if @state.result?.error
                 <Alert bsStyle="danger">


remove trailing slashes on frontend

remove trailing slashes on frontend

see http://questhub.io/realm/meta/quest/525602ec7feb4a6b11000076

--- www/src/routers/not-found.coffee
+++ www/src/routers/not-found.coffee
@@ -5,5 +5,10 @@
         routes:
             "*path": "notFound"

-        notFound: ->
+        notFound: (path) ->
+            if path.match "/$"
+                path = path.replace /\/$/, ''
+                Backbone.history.navigate path, replace: true, trigger: true
+                return
+
             @appView.setPageView new NotFound


Remove ramda-repl - no longer used

Remove ramda-repl - no longer used

--- atom/packages.cson
+++ atom/packages.cson
@@ -43,7 +43,6 @@
   "plantuml-preview"
   "prettier-atom"
   "project-manager"
-  "ramda-repl"
   "react-es6-snippets"
   "relative-numbers"
   "ruby-test"


Make RewriteCond snippet variable name a tab stop

--- atom/snippets.cson
+++ atom/snippets.cson
@@ -865,7 +865,7 @@

   'RewriteCond':
     'prefix': 'rewritecond'
-    'body': 'RewriteCond ${1:%{HTTP_HOST}} ${2:pattern}'
+    'body': 'RewriteCond %{${1:HTTP_HOST}} ${2:pattern}'

   'RewriteRule':
     'prefix': 'rewriterule'


updated replace too

updated replace too

--- grunt/text-replace.coffee
+++ grunt/text-replace.coffee
@@ -10,7 +10,7 @@
           from: 'styles.css'
           to: 'styles-<%= grunt.config("git-commit") %>.css'
         ,
-          from: '/assets/vendor/normalize-css/normalize.css'
+          from: 'assets/vendor/normalize-css/normalize.css'
           to: '//cdnjs.cloudflare.com/ajax/libs/normalize/2.1.3/normalize.min.css'
       ]


ensure compression enabled at right point

--- src/smc-hub/hub_http_server.coffee
+++ src/smc-hub/hub_http_server.coffee
@@ -78,6 +78,12 @@
     router = express.Router()
     app    = express()
     app.use(cookieParser())
+
+    # Enable compression, as
+    # suggested by http://expressjs.com/en/advanced/best-practice-performance.html#use-gzip-compression
+    # NOTE "Express runs everything in order" -- https://github.com/expressjs/compression/issues/35#issuecomment-77076170
+    compression = require('compression')
+    app.use(compression())

     # Very large limit, since can be used to send, e.g., large single patches, and
     # the default is only 100kb!  https://github.com/expressjs/body-parser#limit-2
@@ -487,11 +493,6 @@
         # proxying to this server) is already trying to do something.
         # I don't know if this sort of multi-level proxying is even possible.

-    # Enable compression, as
-    # suggested by http://expressjs.com/en/advanced/best-practice-performance.html#use-gzip-compression
-    compression = require('compression')
-    app.use(compression())
-
     http_server = http.createServer(app)
     return {http_server:http_server, express_router:router}


change level log on info

change level log on info

--- server/utils/logging.coffee
+++ server/utils/logging.coffee
@@ -12,7 +12,7 @@
 # else if process.env.NODE_ENV is 'production' then 1
 # else 0

-LOG_LEVEL = 0
+LOG_LEVEL = 1

 lastLogs = new Array(15)
 lastDate = +new Date()


Add second sort criteria in spec.

Add second sort criteria in spec.

--- src/spec/services/base.spec.coffee
+++ src/spec/services/base.spec.coffee
@@ -120,15 +120,16 @@

       it 'should build query string', ->
         queryString = @service
-          .where('name(en="Foo")')
-          .where('id="1234567890"')
-          .whereOperator('or')
-          .page(3)
-          .perPage(25)
-          .sort('attrib', false)
+          .where 'name(en="Foo")'
+          .where 'id="1234567890"'
+          .whereOperator 'or'
+          .page 3
+          .perPage 25
+          .sort 'attrib', false
+          .sort 'createdAt'
           ._queryString()

-        expect(queryString).toBe 'where=name(en%3D%22Foo%22)%20or%20id%3D%221234567890%22&limit=25&offset=50&sort=attrib%20desc'
+        expect(queryString).toBe 'where=name(en%3D%22Foo%22)%20or%20id%3D%221234567890%22&limit=25&offset=50&sort=attrib%20desc&sort=createdAt%20asc'

       it 'should reset params after resolving a promise', ->
         spyOn(@restMock, 'GET').andCallFake (endpoint, callback) -> callback(null, {statusCode: 200}, {foo: 'bar'})


fix(server): build option httpProxy http verbs like put and post

--- src/src/server/start-server.coffee
+++ src/src/server/start-server.coffee
@@ -16,19 +16,25 @@
 server = {}
 server.express    = express
 server.app        = server.express()
-server.middleware = { bodyParser }
+server.middleware = {}
 server.paths      = client: clientDirPath, server: serverDirPath
 server.server     = require('./defaults/app-listener') server, config

-# load defaults
-# =============
+# load default settings
+# =====================
 require('./defaults/app-settings') server, config
+
+# load optional http proxy middleware (must go before bodyParser)
+# ===================================
+if config.httpProxy.length
+	proxyFilePath = path.join dir, 'options', 'http-proxy.js'
+	httpProxy     = require(proxyFilePath) server.app, config
+	server.middleware.httpProxy = httpProxy
+
+# load default middleware
+# =======================
+server.middleware.bodyParser = bodyParser
 require('./defaults/app-middleware') server, config
-
-# load optional http proxy
-# ========================
-proxyFilePath = path.join dir, 'options', 'http-proxy.js'
-require(proxyFilePath) server.app, config if config.httpProxy.length

 # load optional app server dist entry script
 # ==========================================


make rectangleTool draggable by filling with "transparent"

--- app/assets/javascripts/components/mark/tools/rectangle-tool/index.cjsx
+++ app/assets/javascripts/components/mark/tools/rectangle-tool/index.cjsx
@@ -10,6 +10,9 @@
 DELETE_BUTTON_ANGLE = 45
 DELETE_BUTTON_DISTANCE = 9 / 10
 DEBUG = false
+
+STROKE_COLOR = '#43bbfd'
+STROKE_WIDTH = 2.5

 module.exports = React.createClass
@@ -142,7 +145,12 @@
       >

         <Draggable onDrag = {@handleMainDrag} >
-          <polyline points={points} strokeWidth={2 / @props.xScale} stroke="orange" fill="none"/>
+          <polyline
+            points={points}
+            strokeWidth={2 / @props.xScale}
+            stroke={STROKE_COLOR}
+            fill="transparent"
+          />
         </Draggable>

         { if @props.selected


Commentary: the musical

Commentary: the musical

--- coffee/react-gui/components.coffee
+++ coffee/react-gui/components.coffee
@@ -105,9 +105,19 @@

 LC.React.UndoButton = React.createClass
   displayName: 'UndoButton'
-  getInitialState: -> {isEnabled: @props.lc.canUndo()}
-  componentDidMount: ->
-    @subscriber = => @setState {isEnabled: @props.lc.canUndo()}
+
+  # We do this a lot, even though it reads as a React no-no.
+  # The reason is that '@props.lc' is a monolithic state bucket for
+  # Literally Canvas, and does not offer opportunities for encapsulation.
+  #
+  # However, this component really does read and write only to the 'undo'
+  # part of the state bucket, and we have to get react to update somehow, and
+  # we don't want the parent to have to worry about this, so it's in @state.
+  getState: -> {isEnabled: @props.lc.canUndo()}
+  getInitialState: -> @getState()
+
+  componentDidMount: ->
+    @subscriber = => @setState @getState()
     @props.lc.on 'drawingChange', @subscriber
   componentWillUnmount: ->
     @props.lc.removeEventListener('drawingChange', @subscriber)


fixed timeline play button on rotated timelines

--- src/viz/helpers/ui/timeline.coffee
+++ src/viz/helpers/ui/timeline.coffee
@@ -147,6 +147,10 @@
     availableWidth = vars.width.value-vars.ui.padding*2
     tickStep       = 1
     textRotate     = 0
+    playbackWidth  = timelineHeight
+
+    if vars.timeline.play.value
+      availableWidth -= playbackWidth + vars.ui.padding

     if timelineWidth > availableWidth
       labelWidth     = yearHeight+vars.ui.padding*2
@@ -175,19 +179,19 @@
       start_x = vars.width.value/2 - timelineWidth/2

     if vars.timeline.play.value
-      start_x += (timelineHeight + vars.ui.padding)/2
+      start_x += (playbackWidth + vars.ui.padding)/2

     playButton = vars.g.timeline.selectAll("rect.d3plus_timeline_play")
       .data if vars.timeline.play.value then [0] else []

     playStyle = (btn) ->
       btn
-        .attr "width", timelineHeight + 1
+        .attr "width", playbackWidth + 1
         .attr "height", timelineHeight + 1
         .attr "fill", vars.ui.color.primary.value
         .attr "stroke", vars.timeline.tick
         .attr "stroke-width", 1
-        .attr "x", start_x - timelineHeight - 1 - vars.ui.padding
+        .attr "x", start_x - playbackWidth - 1 - vars.ui.padding
         .attr "y", vars.ui.padding

     playButton.enter().append("rect")
@@ -217,7 +221,7 @@
       text
         .attr "fill", textColor vars.ui.color.primary.value
         .attr textStyle
-        .attr "x", start_x - (timelineHeight - 1)/2 - vars.ui.padding
+        .attr "x", start_x - (playbackWidth - 1)/2 - vars.ui.padding
         .attr "y", y
         .attr "dy", "0.5ex"
         .call playIconChar, if playInterval then "pause" else "icon"


Recorded previous implementation

Recorded previous implementation

--- batch_utils.coffee
+++ batch_utils.coffee
@@ -43,3 +43,46 @@
       $sort: parsed_query.$sort or 'id' # TODO: generalize sort for different types of sync
     }, parsed_query.find) # add find parameters
     runBatch(batch_cursor, callback)
+
+
+# EventEmitter = require('events').EventEmitter
+# Queue = require 'queue-async'
+
+# Query = require './query'
+
+# module.exports = class BatchUtils
+#   @processModels: (model_type, options={}, callback, per_model_fn) ->
+#     event_emitter = new EventEmitter()
+
+#     parallelism = options.parallelism or 1
+#     total_processed = 0
+#     is_done = false
+
+#     query = {$offset: 0, $sort: [['id', 'asc']]}
+#     (query[key] = value if key[0] is '$') for key, value of options
+#     limit = query.$limit or -1 # limit is for total number of items
+#     query.$limit = options.batch_size or Math.max(parallelism, 1) # batch size is per iteration
+
+#     runBatch = (query, callback) ->
+
+#       db_query = new Query(model_type, query)
+#       db_query.toModels (err, models) ->
+#         return callback(new Error("Failed to get models")) if err or !models
+#         (is_done = true; return callback()) unless models.length
+
+#         # batch operations on each
+#         queue = new Queue(parallelism)
+
+#         for model in models
+#           (is_done = true; return callback()) if limit >= 0 and (total_processed >= limit) # done
+#           total_processed++
+#           do (model) -> queue.defer (callback) -> per_model_fn(model, callback)
+
+#         queue.await (err) ->
+#           return callback(err) if err
+#           return callback(null, total_processed) if is_done
+#           query.$offset += total_processed
+#           runBatch(query, callback)
+
+#     runBatch(query, callback)
+#     return event_emitter


FinderController: remove not used methods

--- client/Finder/filetree/controllers/findercontroller.coffee
+++ client/Finder/filetree/controllers/findercontroller.coffee
@@ -88,10 +88,6 @@
         else @noMachineFoundWidget.show()

-  checkVMState: (err, vm, info)->
-    return warn err if err or not info
-    switch info.state
-      when "MAINTENANCE" then @unmountVm vm
   mountMachine:(machine, fetchContent = yes)->

     unless machine.status.state is Machine.State.Running
@@ -258,7 +254,6 @@
     uploader.setPath path
     uploaderPlaceholder.show()

-  _pipedVmName:(vmName)-> vmName.replace /\./g, '|'

   # Filetree helpers
   #


SidebarWorkspaceItem: Listen for WorkspaceDeleted event and forward it.

--- client/Social/Activity/sidebar/sidebarworkspaceitem.coffee
+++ client/Social/Activity/sidebar/sidebarworkspaceitem.coffee
@@ -36,7 +36,9 @@
     left        = x + w + 16
     position    = { top, left }

-    new WorkspaceSettingsPopup { position, delegate: this }
+    settingsPopup = new WorkspaceSettingsPopup { position, delegate: this }
+    settingsPopup.once 'WorkspaceDeleted', (wsId) =>
+      @emit 'WorkspaceDeleted', wsId

   setUnreadCount: (unreadCount = 0) ->


virtualization controller: vm creation: don't be over-optimistic

wait until the vm is actually created before reporting that the creation was successful (since it's possible that vm creation will fail for various reasons).

--- client/landing/app/MainApp/VirtualizationController.coffee
+++ client/landing/app/MainApp/VirtualizationController.coffee
@@ -77,8 +77,9 @@
       title    : "Destroy '#{hostnameAlias}'"
       action   : "Destroy my VM"
       callback : =>
-        @deleteVmByHostname hostnameAlias, callback
-        new KDNotificationView title:'Successfully destroyed!'
+        @deleteVmByHostname hostnameAlias, (err) ->
+          return if KD.showError err
+          new KDNotificationView title:'Successfully destroyed!'
         modal.destroy()
     , vmPrefix

@@ -103,11 +104,8 @@
           new KDNotificationView title: message
           callback { message }

-        else if vmInfo.planCode is 'free'
+        else
           @confirmVmDeletion vmInfo
-
-        else
-          @confirmVmDeletion vmInfo, -> debugger

       else
         new KDNotificationView title: 'Failed to remove!'


Fix account view spec

Fix account view spec

--- components/inquiry_questionnaire/test/views/account.coffee
+++ components/inquiry_questionnaire/test/views/account.coffee
@@ -10,7 +10,7 @@

 describe 'Account', setup ->
   beforeEach ->
-    @view = new Account user: @loggedOutUser, artwork: @artwork, state: @state
+    @view = new Account user: @loggedOutUser, artwork: @artwork, state: @state, inquiry: @inquiry

   describe '#render', ->
     beforeEach ->
@@ -18,9 +18,9 @@

     it 'renders the login page the first time it renders', ->
       @view.$('.iq-headline').text()
-        .should.containEql 'To skip these steps on your next inquiry'
+        .should.containEql 'Create an account to send your message'
       @view.$('input').map(-> $(this).attr('name')).get()
-        .should.eql ['password']
+        .should.eql ['name', 'email', 'password']

     it 're-renders when the mode changes', ->
       @view.active.set 'mode', 'forgot'
@@ -31,9 +31,9 @@
       @view.$('.iq-headline').text()
         .should.containEql 'Please log in to save information to your profile'

-      @view.active.set 'mode', 'signup'
+      @view.active.set 'mode', 'register'
       @view.$('.iq-headline').text()
-        .should.containEql 'To skip these steps on your next inquiry'
+        .should.containEql 'Create an account to send your message'

   describe '#forgot', ->
     beforeEach ->


Add editor.fontFamily config default

Add editor.fontFamily config default

This causes it to display in the settings view.

--- src/editor-view.coffee
+++ src/editor-view.coffee
@@ -26,6 +26,7 @@
 class EditorView extends View
   @characterWidthCache: {}
   @configDefaults:
+    fontFamily: ''
     fontSize: 20
     showInvisibles: false
     showIndentGuide: false


Read scrollView width prior to touching the DOM to prevent reflow

--- src/editor-view.coffee
+++ src/editor-view.coffee
@@ -896,7 +896,7 @@
       @clearCharacterWidthCache()
       @requestDisplayUpdate()

-  updateLayerDimensions: ->
+  updateLayerDimensions: (scrollViewWidth) ->
     height = @lineHeight * @editor.getScreenLineCount()
     unless @layerHeight == height
       @layerHeight = height
@@ -906,7 +906,7 @@
       @verticalScrollbarContent.height(@layerHeight)
       @scrollBottom(height) if @scrollBottom() > height

-    minWidth = Math.max(@charWidth * @editor.getMaxScreenLineLength() + 20, @scrollView.width())
+    minWidth = Math.max(@charWidth * @editor.getMaxScreenLineLength() + 20, scrollViewWidth)
     unless @layerMinWidth == minWidth
       @renderedLines.css('min-width', minWidth)
       @underlayer.css('min-width', minWidth)
@@ -957,7 +957,8 @@
       @redrawOnReattach = true
       return

-    @updateRenderedLines()
+    scrollViewWidth = @scrollView.width()
+    @updateRenderedLines(scrollViewWidth)
     @updatePlaceholderText()
     @highlightCursorLine()
     @updateCursorViews()
@@ -1028,7 +1029,7 @@
       else
         @underlayer.append($('<span/>', class: 'placeholder-text', text: @placeholderText))

-  updateRenderedLines: ->
+  updateRenderedLines: (scrollViewWidth) ->
     firstVisibleScreenRow = @getFirstVisibleScreenRow()
     lastScreenRowToRender = firstVisibleScreenRow + @heightInLines - 1
     lastScreenRow = @editor.getLastScreenRow()
@@ -1052,7 +1053,7 @@
     @fillDirtyRanges(intactRanges, renderFrom, renderTo)
     @firstRenderedScreenRow = renderFrom
     @lastRenderedScreenRow = renderTo
-    @updateLayerDimensions()
+    @updateLayerDimensions(scrollViewWidth)
     @updatePaddingOfRenderedLines()

   computeSurroundingEmptyLineChanges: (change) ->


engine data module

engine data module

--- src/engine_data.coffee
+++ src/engine_data.coffee
@@ -15,8 +15,4 @@
     get: ->
       I.data

-  get: (key) ->
-    I.data[key]
-
-  set: (key, value) ->
-    I.data[key] = value
+  {}


(refactor) Changed the call according to new sandbox API.

--- src/flavors/api.coffee
+++ src/flavors/api.coffee
@@ -1,13 +1,18 @@

 module.exports =
   api: ->
-    anyGraph: (msg, fn) ->
-      any = false;
-      graphs.forEach (g) ->
-        try
-          fn g
-          any = true
-        catch e
-
-      if !any
-        throw msg
+    snippets:
+      anyGraph: """function(msg,fn){it(msg, function(){
+          var any = false;
+          graphs.forEach(function(g){
+            try {
+              fn(g);
+              any = true;
+            }
+            catch(e){}
+          });
+          if(!any){
+            throw msg;
+          }
+        });}
+        """


IE11 doesn't support Array.find so use underscore instead

--- services/web/public/coffee/ide/editor/directives/aceEditor/spell-check/HighlightedWordManager.coffee
+++ services/web/public/coffee/ide/editor/directives/aceEditor/spell-check/HighlightedWordManager.coffee
@@ -52,7 +52,7 @@
 				@removeHighlight(highlight)

 		findHighlightWithinRange: (range) ->
-			@highlights.find (highlight) =>
+			_.find @highlights, (highlight) =>
 				@_doesHighlightOverlapRange highlight, range.start, range.end

 		_doesHighlightOverlapRange: (highlight, start, end) ->
@@ -69,7 +69,7 @@
 			!(highlightIsAllBeforeRange or highlightIsAllAfterRange)

 		clearHighlightTouchingRange: (range) ->
-			highlight = @highlights.find (hl) =>
+			highlight = _.find @highlights, (hl) =>
 				@_doesHighlightTouchRange hl, range.start, range.end
 			if highlight
 				@removeHighlight highlight


META-66: Allow jira searches with only labels

--- src/jira/search.coffee
+++ src/jira/search.coffee
@@ -10,17 +10,19 @@
       labels = (query.match(Config.labels.regex).map((label) -> label.replace('#', '').trim())).concat(labels)
       query = query.replace Config.labels.regex, ""

-    jql = "text ~ \"#{query}\""
+    jql = if query.length > 0 then "text ~ \"#{query}\"" else ""
     noResults = "No results for #{query}"
     found = "Found __xx__ issues containing `#{query}`"

     if project
-      jql += " and project = #{project}"
+      jql += " and " if jql.length > 0
+      jql += "project = #{project}"
       noResults += " in project `#{project}`"
       found += " in project `#{project}`"

     if labels.length > 0
-      jql += " and labels = #{label}" for label in labels
+      jql += " and " if jql.length > 0
+      jql += "labels = #{label}" for label in labels
       noResults += " with labels `#{labels.join ', '}`"
       found += " with labels `#{labels.join ', '}`"


Support fractional victories

Support fractional victories

Can set the score to 0.5, for example
hubot set King of Tokyo leaderboard Christopher 0.5

--- src/leaderboard.coffee
+++ src/leaderboard.coffee
@@ -85,10 +85,10 @@
       else
         msg.send "We have a winner"

-  robot.respond /set( the)? (.*) leaderboard (.*) (\d*)/i, (msg) ->
+  robot.respond /set( the)? (.*) leaderboard (.*) ([\d\.]*)/i, (msg) ->
     event = msg.match[2]
     name = msg.match[3]
-    score = parseInt(msg.match[4], 10)
+    score = parseFloat(msg.match[4], 10)
     users = robot.brain.usersForFuzzyName name
     if isNaN(score)
       return


Formatting.

Formatting.

--- src/map-printer.coffee
+++ src/map-printer.coffee
@@ -37,7 +37,7 @@
             window.onafterprint = => @afterPrint()

         printMap: (notOnBeforePrint) =>
-            if !notOnBeforePrint and !document.getElementById('map-as-png')
+            if !notOnBeforePrint and !document.getElementById 'map-as-png'
                 alert i18n.t 'print.use_print_button'
                 return
             if @makingPrint or @printed == false
@@ -56,7 +56,8 @@
             vid = 0
             for own id, marker of markers
                 # Settings altered for printing. These will be reset after printing.
-                printStore = {storeAttributes: ['iconSize', 'iconAnchor'] }
+                printStore =
+                    storeAttributes: ['iconSize', 'iconAnchor']
                 for att in printStore.storeAttributes
                     if marker.options.icon.options[att]
                         printStore[att] = marker.options.icon.options[att]
@@ -71,8 +72,8 @@
                 # map.getBounds and map._originalGetBounds both give the bounds
                 # of the active area. -> Need to get whole #map bounds manually.
                 bounds = map.getPixelBounds()
-                sw = map.unproject(bounds.getBottomLeft())
-                ne = map.unproject(bounds.getTopRight())
+                sw = map.unproject bounds.getBottomLeft()
+                ne = map.unproject bounds.getTopRight()

                 if mapBounds.contains marker.getLatLng()
                     marker.vid = ++vid
@@ -115,6 +116,7 @@
                         div.textContent = marker.unit.attributes.name[p13n.getLanguage()]
                         markerLegend.appendChild div
                     listOfUnits.appendChild(markerLegend)
+
             leafletImage map, (err, canvas) =>
                 if err
                     throw err


Stop Propagation when mousedown in nodes,

--- src/node-editor.coffee
+++ src/node-editor.coffee
@@ -96,7 +96,7 @@
     @nodeElement = @svgToolParent.circle(@nodeSize)
     @nodeElement.node.setAttribute('class', 'node-editor-node')

-    @nodeElement.click (e) =>
+    @nodeElement.mousedown (e) =>
       e.stopPropagation()
       @setEnableHandles(true)
       @pathEditor.activateNode(@node)
@@ -131,7 +131,7 @@
     @handleElements.members[0].node.setAttribute('class', 'node-editor-handle')
     @handleElements.members[1].node.setAttribute('class', 'node-editor-handle')

-    @handleElements.click (e) =>
+    @handleElements.mousedown (e) =>
       e.stopPropagation()
       false


Remove flipNegate from minimatch options.

--- src/path-filter.coffee
+++ src/path-filter.coffee
@@ -5,7 +5,7 @@
 # Public: {PathFilter} makes testing for path inclusion easy.
 module.exports =
 class PathFilter
-  @MINIMATCH_OPTIONS: { matchBase: true, dot: true, flipNegate: true }
+  @MINIMATCH_OPTIONS: { matchBase: true, dot: true }

   @escapeRegExp: (str) ->
     str.replace(/([\/'*+?|()\[\]{}.\^$])/g, '\\$1')


working towards better expand behaviour

working towards better expand behaviour

--- app/assets/javascripts/ckeditor/plugins/effective_menus/plugin.js.coffee
+++ app/assets/javascripts/ckeditor/plugins/effective_menus/plugin.js.coffee
@@ -3,22 +3,48 @@
   class EffectiveMenuEditor
     defaults:
       menuClass: 'effective-menu'
+      expandThreshold: 1000

     menu: null
     draggable: null
+    droppable: null

     constructor: (el, options) ->
       @options = $.extend({}, @defaults, options)
       @menu = $(el)

-      @initDragDropEvents()
       @initCkEditorEvents()
       @initAdditionalEvents()
+      @initDragDropEvents()
       true

+    initCkEditorEvents: ->
+      @menu.on 'click', 'li.dropdown.open', (event) ->
+        event.stopPropagation()
+        CKEDITOR.instances[Object.keys(CKEDITOR.instances)[0]].openDialog 'effectiveMenusDialog', (dialog) ->
+          dialog.effective_menu_item = $(event.currentTarget)
+
+      @menu.on 'click', '.dropdown-menu > li.dropdown', (event) ->
+        event.stopPropagation()
+        CKEDITOR.instances[Object.keys(CKEDITOR.instances)[0]].openDialog 'effectiveMenusDialog', (dialog) ->
+          dialog.effective_menu_item = $(event.currentTarget)
+
+      @menu.on 'click', 'li:not(.dropdown)', (event) ->
+        event.stopPropagation()
+        CKEDITOR.instances[Object.keys(CKEDITOR.instances)[0]].openDialog 'effectiveMenusDialog', (dialog) ->
+          dialog.effective_menu_item = $(event.currentTarget)
+
+    initAdditionalEvents: ->
+      @menu.on 'click', 'a', (event) -> event.preventDefault()
+
     initDragDropEvents: ->
-      @menu.on 'dragenter', 'li', (event) => event.preventDefault() if @draggable  # enable drag and drop
-      @menu.on 'dragleave', 'li', (event) => event.preventDefault() if @draggable # enable drag and drop
+      @menu.on 'dragenter', 'li', (event) =>
+        @droppable = null
+        @droppable = {item: $(event.currentTarget), time: new Date().getTime()}
+        event.preventDefault() if @draggable  # enable drag and drop
+
+      @menu.on 'dragleave', 'li', (event) =>
+        event.preventDefault() if @draggable # enable drag and drop

       @menu.on 'dragstart', 'li', (event) =>
         @draggable = node = $(event.currentTarget)
@@ -29,15 +55,22 @@
         event.stopPropagation()

       @menu.on 'dragover', 'li', (event) =>
+        node = $(event.currentTarget)
+
         return false unless @draggable
-
-        node = $(event.currentTarget)
+        return false if @draggable.find(node).length > 0 # Can't drag a parent into a child
+
+        console.log 'dragover'

         if (node.hasClass('dropdown') || node.hasClass('dropdown-submenu')) && !node.hasClass('open') # This is a menu, expand it
           @menu.find('.open').removeClass('open')
           node.parentsUntil(@menu, 'li').andSelf().addClass('open')
         else
-          event.preventDefault()
+          event.preventDefault() # Enable drag and drop
+
+          # If I've been hovering for expandThreshold, expand the node to have a dropdown
+          if @droppable.time? && ((new Date().getTime()) - @droppable.time) > @options.expandThreshold
+            @convertToDropdown(node)

         # If I don't have the placeholder class already
         if node.hasClass('placeholder') == false
@@ -51,8 +84,14 @@

         node = $(event.currentTarget)
         node.css('opacity', '1.0')
-        @menu.removeClass('dragging').find('.placeholder').removeClass('placeholder')
+        @menu.removeClass('dragging')
+
+        @menu.find('.placeholder').removeClass('placeholder')
+        @menu.find('.effective-menu-expand').remove()
+        @menu.find('.open').removeClass('open')
+
         @draggable = null
+        @droppable = null

       @menu.on 'drop', 'li', (event) =>
         return false unless @draggable
@@ -62,26 +101,26 @@
         node.before(event.originalEvent.dataTransfer.getData('text/html'))

         @menu.removeClass('dragging').find('.placeholder').removeClass('placeholder')
+        @menu.find('.effective-menu-expand').remove()
+        @menu.find('.open').removeClass('open')
+        node.parentsUntil(@menu, 'li').addClass('open')

         @draggable.remove() if @draggable
         @draggable = null
+        @droppable = null

         event.stopPropagation()
         event.preventDefault()

-    initCkEditorEvents: ->
-      @menu.on 'click', 'li.dropdown.open', (event) ->
-        event.stopPropagation()
-        CKEDITOR.instances[Object.keys(CKEDITOR.instances)[0]].openDialog 'effectiveMenusDialog', (dialog) ->
-          dialog.effective_menu_item = $(event.currentTarget)
-
-      @menu.on 'click', 'li:not(.dropdown)', (event) ->
-        event.stopPropagation()
-        CKEDITOR.instances[Object.keys(CKEDITOR.instances)[0]].openDialog 'effectiveMenusDialog', (dialog) ->
-          dialog.effective_menu_item = $(event.currentTarget)
-
-    initAdditionalEvents: ->
-      @menu.on 'click', 'a', (event) -> event.preventDefault()
+    convertToDropdown: (node) ->
+      return false if node.hasClass('dropdown') || node.hasClass('effective-menu-expand') #|| node.children('.expand').length > 0
+
+      node.append(@menu.data('effective-menu-expand-html'))
+      node.addClass('dropdown').addClass('open')
+      console.log node.children('a')
+
+      node.children('a').attr('data-toggle', 'dropdown')
+

     serialize: (retval) ->

@@ -131,6 +170,7 @@
       parent.children('.menu-item').children("input[name$='[rgt]']").val(rgt)

       rgt + 1
+

   $.fn.extend effectiveMenuEditor: (option, args...) ->
     @each ->


add blip.tv, DailyMotion, Funny or Die and PhotoBucket to oembed scraper

--- scripts/preview/oembed.coffee
+++ scripts/preview/oembed.coffee
@@ -49,8 +49,10 @@
       @pass()

 do ->
+  provider = -> OEmbedScraper.registerProvider arguments...
+
   # List of providers found at <http://www.oembed.com/#section7>.
-  provider = -> OEmbedScraper.registerProvider arguments...
+  ##

   provider 'YouTube', 'http://www.youtube.com/oembed'
     scheme: (uri) -> uri.isInDomain('youtube.com') or uri.host is 'youtu.be'
@@ -80,3 +82,24 @@

   provider 'SlideShare', 'http://www.slideshare.net/api/oembed/2'
     scheme: (uri) -> uri.isInDomain('slideshare.net') and uri.globPath('/*/*')
+
+  # Additional providers found on my many voyages across this vast Internet.
+  # Ordered alphabetically for convenience and for the sake of not showing
+  # favoritism.
+  ##
+
+  provider 'Blip.tv', 'http://blip.tv/oembed/'
+    scheme: (uri) -> uri.isInDomain('blip.tv') and uri.globPath('/file/*')
+
+  provider 'DailyMotion', 'http://www.dailymotion.com/api/oembed'
+    scheme: (uri) -> uri.isInDomain 'dailymotion.com'
+
+  provider 'Funny or Die', 'http://www.funnyordie.com/oembed'
+    scheme: (uri) -> uri.isInDomain('funnyordie.com') and uri.globPath('/videos/*')
+
+  provider 'PhotoBucket', 'http://photobucket.com/oembed'
+    scheme: (uri) ->
+      uri.isInDomain('photobucket.com') and
+      (uri.globPath('/albums/*') or uri.globPath('/groups/*'))
+
+


boyscout

boyscout

--- core/app/backbone/views/evidence/evidence_vote_view.coffee
+++ core/app/backbone/views/evidence/evidence_vote_view.coffee
@@ -25,8 +25,8 @@
     ].join(' ')

     comment_votes_amount =
-        R.span className:"comment-vote-amount spec-evidence-relevance",
-          format_as_short_number(@model().relevance())
+      R.span className:"comment-vote-amount spec-evidence-relevance",
+        format_as_short_number(@model().relevance())

     if Factlink.Global.signed_in
       R.div className: 'comment-votes',


Client version

Client version

--- server/d2mpclient/clientver.coffee
+++ server/d2mpclient/clientver.coffee
@@ -3,8 +3,8 @@
   if clientParams.find({stype: "version"}).count() < 1
     clientParams.insert
       stype: "version"
-      version: "0.1.3"
-      url: "https://s3-us-west-2.amazonaws.com/d2mpclient/0.1.3.zip"
+      version: "0.1.8"
+      url: "https://s3-us-west-2.amazonaws.com/d2mpclient/0.1.8.zip"

 Router.map ->
   @route 'clientver',


#getButtonAttributeName -> #getAttributeName

--- src/trix/toolbar_controller.coffee
+++ src/trix/toolbar_controller.coffee
@@ -12,7 +12,7 @@

   didClickButton: (event, element) =>
     event.preventDefault()
-    attributeName = getButtonAttributeName(element)
+    attributeName = getAttributeName(element)

     if @getDialogForAttributeName(attributeName)
       @showDialog(attributeName)
@@ -25,7 +25,7 @@
     @[method].call(this, dialogElement)

   setAttribute: (dialogElement) ->
-    attributeName = getButtonAttributeName(dialogElement)
+    attributeName = getAttributeName(dialogElement)
     value = getInputForDialog(dialogElement).value
     @delegate?.toolbarDidUpdateAttribute(attributeName, value)

@@ -43,7 +43,7 @@

   eachButton: (callback) ->
     for element in @element.querySelectorAll(buttonSelector)
-      callback(element, getButtonAttributeName(element))
+      callback(element, getAttributeName(element))

   showDialog: (attributeName) ->
     @delegate?.toolbarWillShowDialog()
@@ -62,9 +62,9 @@
   getDialogForAttributeName: (attributeName) ->
     @element.querySelector(".dialog[data-attribute=#{attributeName}]")

-  getButtonAttributeName = (element) ->
+  getAttributeName = (element) ->
     element.getAttribute("data-attribute")

   getInputForDialog = (element) ->
-    attributeName = getButtonAttributeName(element)
+    attributeName = getAttributeName(element)
     element.querySelector("input[name='#{attributeName}']")


Updated tests

Updated tests

--- test/index.coffee
+++ test/index.coffee
@@ -56,11 +56,11 @@
             else unless typeof obj.password is 'string'
                return cb unrecognizedOptionsError

-            if obj.user.user? # username based
-               if obj.user.user is user and
+            if obj.user.username? # username based
+               if obj.user.username is user and
                  (obj.password is okpass or obj.password.digest is goodDigest)
                   return cb null, { token: goodToken }
-               else if obj.user.user is srpUser
+               else if obj.user.username is srpUser
                   if obj.srp? and obj.password.digest is goodDigest
                      if obj.srp is 'c398c8dce734b1a6e7959bf90067985a1291e9f32a62edf15efb5adc760e40ce'
                         return cb null, { token: migrationToken }


Share standard props with label

Share standard props with label

--- src/components/cc-dashboard/blank-course.cjsx
+++ src/components/cc-dashboard/blank-course.cjsx
@@ -9,6 +9,7 @@
     courseId: React.PropTypes.string

   render: ->
+    glprops = {lowercase:true, courseId: @props.courseId}

         Welcome to your OpenStax Concept Coach™ Dashboard
@@ -18,20 +19,20 @@

-              Add <CourseGroupingLabel plural courseId={@props.courseId} /> to
+              Add <CourseGroupingLabel plural {...glprops} /> to
               your course by clicking on your name in the top
               right corner and selecting "Course Settings and Roster."

               Generate a student enrollment code for
-              each <CourseGroupingLabel courseId={@props.courseId} /> you
+              each <CourseGroupingLabel {...glprops} /> you
               create.

               Distribute the enrollment codes for
-              each <CourseGroupingLabel courseId={@props.courseId} /> and
+              each <CourseGroupingLabel {...glprops} /> and
               textbook URL (which is the same for
-              each <CourseGroupingLabel courseId={@props.courseId} />) to
+              each <CourseGroupingLabel {...glprops} />) to
               your students.


stop the url flicker on breadcrumb naving

--- src/components/task-teacher-review/index.cjsx
+++ src/components/task-teacher-review/index.cjsx
@@ -63,14 +63,17 @@
     scrollPointData = _.extend({scrollPoint: scrollPoint}, scrollState)
     @state.scrollPoints.push(scrollPointData)

-  getScrollStateByScroll: (scrollTop) ->
+  sortScrollPoints: ->
     sortedDescScrollPoints = _.sortBy @state.scrollPoints, (scrollData) ->
       -1 * scrollData.scrollPoint

-    scrollState = _.find sortedDescScrollPoints, (scrollData) =>
+    @setState({scrollPoints: sortedDescScrollPoints})
+
+  getScrollStateByScroll: (scrollTop) ->
+    scrollState = _.find @state.scrollPoints, (scrollData) =>
       scrollTop > (scrollData.scrollPoint - @state.scrollTopBuffer)

-    scrollState or _.last(sortedDescScrollPoints)
+    scrollState or _.last(@state.scrollPoints)

   getScrollStateByKey: (stepKey) ->
     scrollState = _.find @state.scrollPoints, (scrollData) ->
@@ -79,16 +82,27 @@
   setScrollState: ->
     scrollState = @getScrollStateByScroll(@state.scrollTop)
     @setState({scrollState})
-    @goToStep(@state.scrollState.key)() if @state.scrollState?.key?
+
+  componentDidMount: ->
+    @sortScrollPoints()
+    @scrollToKey(@state.currentStep)
+    @setScrollState()
+
+  componentWillUpdate: (nextProps, nextState) ->
+    willScrollStateKeyChange = not (nextState.scrollState.key is @state.scrollState.key)
+    @goToStep(nextState.scrollState.key)() if willScrollStateKeyChange

   componentDidUpdate: (prevProps, prevState) ->
-    didScrollStateChange = not (prevState.scrollState.key is @getScrollStateByScroll(@state.scrollTop).key)
+    doesScrollStateMatch = (prevState.scrollState.key is @getScrollStateByScroll(@state.scrollTop).key)
     didCurrentStepChange = not (@state.currentStep is prevState.scrollState?.key)
+    didScrollPointsChange = not (prevState.scrollPoints.length is @state.scrollPoints.length)

-    if didCurrentStepChange and not didScrollStateChange
+    @sortScrollPoints() if didScrollPointsChange
+
+    unless doesScrollStateMatch
+      @setScrollState()
+    else if didCurrentStepChange
       @scrollToKey(@state.currentStep)
-    else if didScrollStateChange
-      @setScrollState()

   scrollToKey: (stepKey) ->
     scrollState = @getScrollStateByKey(stepKey)
@@ -103,7 +117,6 @@
       params.stepIndex = stepKey + 1
       params.id = @props.id # if we were rendered directly, the router might not have the id

-      # @context.router.transitionTo('reviewTaskStep', params)
       @context.router.replaceWith('reviewTaskStep', params)

   getCrumb: (crumbKey) ->


update

update

--- src/copy-jizuradb-to-Hollerith2-format.coffee
+++ src/copy-jizuradb-to-Hollerith2-format.coffee
@@ -412,17 +412,6 @@
   seen_glyphs       = new Set()
   factor_count      = 0
   #.........................................................................................................
-  # resolve_ics_to_factors = ( glyph ) =>
-  #   return if seen_glyphs.has glyph
-  #   seen_glyphs.add glyph
-  #   return unless factors.has glyph
-  #   if ( entry = factors_by_factor[ glyph ] )?
-  #     for ic in Array.from entry.keys()
-  #       resolve_ics_to_factors ic
-  #       if ( sub_entry = factors_by_factor[ ic ] )?
-  #         for sub_ic in Array.from sub_entry
-  #           entry.add sub_ic
-  #.........................................................................................................
   return $ ( phrase, send, end ) =>
     #.......................................................................................................
     if phrase?
@@ -447,15 +436,15 @@
       njs_fs.writeFileSync ics_route, factors_by_factor_json
       warn "write to #{factors_route}"
       njs_fs.writeFileSync factors_route, factors_json
-      #.....................................................................................................
-      for factor in Array.from factors.keys()
-        unless ( entry = factors_by_factor[ factor ] )?
-          whisper "no ICs for factor #{factor}"
-          continue
-        for ics in entry
-          for ic in ics
-            unless factors.has ic
-              warn "factor #{factor} has non-factorial IC #{ic}"
+      # #.....................................................................................................
+      # for factor in Array.from factors.keys()
+      #   unless ( entry = factors_by_factor[ factor ] )?
+      #     whisper "no ICs for factor #{factor}"
+      #     continue
+      #   for ics in entry
+      #     for ic in ics
+      #       unless factors.has ic
+      #         warn "factor #{factor} has non-factorial IC #{ic}"
       #.....................................................................................................
       end()
     #.......................................................................................................


Hound linter fix

Hound linter fix

--- app/assets/javascripts/application.js.coffee
+++ app/assets/javascripts/application.js.coffee
@@ -62,7 +62,7 @@
         if $(this).closest(".question-block").find(".errors-container li").size() > 0
           $(this).closest(".question-block").find(".errors-container").empty()
       $(this).closest(".question-has-errors").removeClass("question-has-errors")
-  $(".supporters-list input").change () ->
+  $(".supporters-list input").change ->
     $(this).closest("label").find(".errors-container").empty()
     $(this).closest(".question-has-errors").removeClass("question-has-errors")


Conflict resolved

Conflict resolved

--- app/assets/javascripts/application.js.coffee
+++ app/assets/javascripts/application.js.coffee
@@ -31,6 +31,11 @@
     buttonImage: "calendar.gif"
     buttonText: "Calendar"
     dateFormat: "dd-mm-yy"
+
+
+  $('.toggle-card-form').click ->
+    target = $(this).data('target')
+    $("##{target}").toggle()

   $(".checklist_check").click ->
     if $(this).is(":checked")


Auto collapses the navigation when resizing Closes #14475

--- app/assets/javascripts/application.js.coffee
+++ app/assets/javascripts/application.js.coffee
@@ -218,13 +218,20 @@
       $this = $(this)
       $this.attr 'value', $this.val()

+  $sidebarGutterToggle = $('.js-sidebar-toggle')
+  $navIconToggle = $('.toggle-nav-collapse')
+
   $(document)
     .off 'breakpoint:change'
     .on 'breakpoint:change', (e, breakpoint) ->
       if breakpoint is 'sm' or breakpoint is 'xs'
-        $gutterIcon = $('.js-sidebar-toggle').find('i')
+        $gutterIcon = $sidebarGutterToggle.find('i')
         if $gutterIcon.hasClass('fa-angle-double-right')
-          $gutterIcon.closest('a').trigger('click')
+          $sidebarGutterToggle.trigger('click')
+
+        $navIcon = $navIconToggle.find('.fa')
+        if $navIcon.hasClass('fa-angle-left')
+          $navIconToggle.trigger('click')

   $(document)
     .off 'click', '.js-sidebar-toggle'


TEMP disable eligibility submit

TEMP disable eligibility submit

--- app/assets/javascripts/application.js.coffee
+++ app/assets/javascripts/application.js.coffee
@@ -102,13 +102,13 @@
   showAwardStep = (step) ->
     $(".js-step-condition.step-current").removeClass("step-current")

-    if ($ 'form.award-form').length && (($ 'form.award-form').data('eligible') == 'false' || !($ 'form.award-form').data('eligible'))
-      window.location.hash = "#eligibility"
-      $(".js-step-condition[data-step='step-eligibility']").addClass("step-current")
-      alert('Sorry, you are not eligible for the award') unless step == 'step-eligibility'
-    else
-      window.location.hash = "##{step.substr(5)}"
-      $(".js-step-condition[data-step='#{step}']").addClass("step-current")
+    #if ($ 'form.award-form').length && (($ 'form.award-form').data('eligible') == 'false' || !($ 'form.award-form').data('eligible'))
+    #  window.location.hash = "#eligibility"
+    #  $(".js-step-condition[data-step='step-eligibility']").addClass("step-current")
+    #  alert('Sorry, you are not eligible for the award') unless step == 'step-eligibility'
+    #else
+    window.location.hash = "##{step.substr(5)}"
+    $(".js-step-condition[data-step='#{step}']").addClass("step-current")

     # Show past link status
     $(".steps-progress-bar .js-step-link.step-past").removeClass("step-past")


Add TODO.

Add TODO.

--- app/assets/javascripts/application.js.coffee
+++ app/assets/javascripts/application.js.coffee
@@ -12,6 +12,7 @@
     ($provide, $httpProvider, $routeProvider, $locationProvider) ->
       $provide.value("alertTimeout", 3000)

+      # TODO refactor this interceptor, see spinner
       httpErrorsInterceptor = ($q, $log, alerts) ->
         (promise) ->
           onSuccess = (response) ->


CoffeeScript style improv.

CoffeeScript style improv.

--- app/assets/javascripts/awards_handler.coffee
+++ app/assets/javascripts/awards_handler.coffee
@@ -11,8 +11,9 @@
         if $(".emoji-menu").is(":visible")
           $(".emoji-menu").hide()

-    $(".awards").off "click"
-    $(".awards").on "click", ".js-emoji-btn", @handleClick
+    $(".awards")
+      .off "click"
+      .on "click", ".js-emoji-btn", @handleClick

     @renderFrequentlyUsedBlock()


Fixed issue with enter key selecting wrong option in dropdown

If you search for a label and press enter the dropdown will select the last element rather than the first. This is because the currentIndex is -1 rather than 0

Closes #17630

--- app/assets/javascripts/gl_dropdown.js.coffee
+++ app/assets/javascripts/gl_dropdown.js.coffee
@@ -460,7 +460,7 @@
         return false

       if currentKeyCode is 13
-        @selectRowAtIndex currentIndex
+        @selectRowAtIndex if currentIndex < 0 then 0 else currentIndex

   removeArrayKeyEvent: ->
     $('body').off 'keydown'


SaveOnChangePage force dirty saving on navigation.

[#60875594]
https://www.pivotaltracker.com/story/show/60875594

--- app/assets/javascripts/save-on-change.coffee
+++ app/assets/javascripts/save-on-change.coffee
@@ -32,53 +32,90 @@

 class SaveOnChange
-  constructor: (@$form) ->
+  changeInterval: 200  # Fire change and blur events almost instantly.
+  keyUpinterval:  2000 # Fire keyup events with a larger delay
+  constructor: (@$form, @page) ->
     @scheduled_job      = null
     @previous_value     = null
-    @update_interval_s  = 2.0
-    @save_indicator     = new SaveIndicator($("#save"))
     @setupEvents()

   setupEvents: ->
-    # Fire inputs field changes on 'change' events with no scheduled_jobs
-    @$form.find('input,textarea,select').on 'change', (e) =>
-      @saveElement()
-    @$form.find('input,textarea,select').on 'blur',   (e) =>
-      @saveElement()
-    @$form.find('input,textarea,select').on 'keyup',  (e) =>
-      @schedule()

-  saveElement: ->
-      data = @$form.serialize()
-      return if @previous_value == data
-      @save_indicator.showSaving()
-      $.ajax({
-        type: "POST",
-        url: @$form.attr( 'action' ),
-        data: @$form.serialize(),
-        success: (response) =>
-          @previous_value = data
-          @save_indicator.showSaved()
-        error: (jqxhr, status, error) =>
-          @save_indicator.showSaveFailed()
-      })
+    @$form.find('input,textarea,select').on 'change blur', (e) =>
+      @schedule(@changeInterval)
+    @$form.find('input,textarea').on 'keyup',  (e) =>
+      @schedule(@keyUpinterval)
+
+  saveElement:(async = true) ->
+    data = @$form.serialize()
+    @page.saving()
+    $.ajax({
+      type: "POST",
+      async: async,
+      url: @$form.attr( 'action' ),
+      data: @$form.serialize(),
+      success: (response) =>
+        @previous_value = data
+        @page.saved(@)
+        @dirty = false
+      error: (jqxhr, status, error) =>
+        @page.failed(@)
+    })
+
+  saveNow: ->
+    @unschedule()
+    @saveElement(false)

   # remove events scheduled for elem
   unschedule: () ->
     clearTimeout(@scheduled_job) if @scheduled_job
     @scheduled_job = null

-  schedule:  () ->
+  schedule:  (interval) ->
     @unschedule() # remove any existing events
-    action = () =>
-      @saveElement()
-    @scheduled_job = setTimeout(action, @update_interval_s * 1000)
+    dirty = (@previous_value != @$form.serialize())
+    if dirty
+      @page.mark_dirty(this)
+      action = () =>
+        @saveElement()
+      @scheduled_job = setTimeout(action, interval)
+
+
+class SaveOnChangePage
+  constructor: () ->
+    @save_indicator = new SaveIndicator($("#save"))
+    @intercept_navigation()
+    @forms = []
+    if $('.live_submit').length
+      $('.live_submit').each (i,e) =>
+        @forms.push(new SaveOnChange($(e),@))
+    @dirty_forms = {}
+
+  intercept_navigation: ->
+    $("a").on 'click', (e) =>
+      @force_save_dirty()
+
+  saving: (form) ->
+    @save_indicator.showSaving()
+
+  saved: (form) ->
+    @save_indicator.showSaved()
+    @mark_clean(form)
+
+  failed: (form) ->
+    @save_indicator.showSaveFailed()
+
+  mark_dirty: (form) ->
+    @dirty_forms[form] = form;
+
+  mark_clean: (form) ->
+    delete @dirty_forms[form]
+
+  force_save_dirty: ->
+    for item, value of @dirty_forms
+      value.saveNow()

 $(document).ready ->
-  window.SaveOnChange = SaveOnChange
-  if $('.live_submit').length
-    $('.live_submit').each (i,e) =>
-      new SaveOnChange($(e))
-  else
-    # construct the indicator so it will enter its clear state
-    indicator = new SaveIndicator($("#save"))
+  window.SaveOnChangePage = SaveOnChangePage
+  window.SaveOnChange     = SaveOnChange
+  new SaveOnChangePage()


Make 'answer_for' event work for all kind of questions (not only required ones)

[#86637182]

--- app/assets/javascripts/save-on-change.coffee
+++ app/assets/javascripts/save-on-change.coffee
@@ -13,6 +13,7 @@
     "embeddable_image_question_answer[answer_text]"
   ]
   constructor: (@$form) ->
+    @required = @$form.find("input.hidden_is_final").length > 0
     @data = []

   filtered_data: (array) ->
@@ -47,14 +48,12 @@

   is_answered: () ->
     @mark_form_clean()
-    hidden_final = @$form.find("input.hidden_is_final")
-    return true unless hidden_final.length > 0
     data = @$form.serializeArray()
     @data = data.filter (obj) ->
       obj.name in CompleteChecker.FieldNames

     if @data.length == 0 # multiple choices
-      @mark_form_missing_required()
+      @mark_form_missing_required() if @required
       return false

     @mark_form_element_clean(obj) for obj in @data
@@ -62,8 +61,9 @@
       !obj.value

     if @unasnwered.length > 0
-      @mark_form_element_missing_required(obj) for obj in @unasnwered
-      @mark_form_missing_required()
+      if @required
+        @mark_form_element_missing_required(obj) for obj in @unasnwered
+        @mark_form_missing_required()
       return false

     @require_is_final()
@@ -211,7 +211,6 @@
     @save_indicator = SaveIndicator.instance()
     @intercept_navigation()
     @forms = []
-    @prevent_forward_navigation_count = 0
     if $('.live_submit').length
       $('.live_submit').each (i,e) =>
         @forms.push(new SaveOnChange($(e),@))


Add listener for updating the redundancy panel

--- admin/static/coffee/dashboard.coffee
+++ admin/static/coffee/dashboard.coffee
@@ -43,6 +43,8 @@
             issues.on 'all', @render
             issues_redundancy.on 'reset', @render # when issues_redundancy is reset
             machines.on 'stats_updated', @render # when the stats of the machines are updated
+            directory.on 'all', @render
+            namespaces.on 'all', @render

             $('.links_to_other_view').live 'click', ->
                 $('.popover-inner').remove()


Styles not lost on drag

Styles not lost on drag

--- app/scripts/alchemy/core/interactions.coffee
+++ app/scripts/alchemy/core/interactions.coffee
@@ -64,6 +64,9 @@
                 alchemy.conf.nodeMouseOut(n)

     nodeClick: (n) ->
+        # Don't consider drag a click
+        return if d3.event.defaultPrevented
+
         d3.event.stopPropagation()
         node = alchemy._nodes[n.id]

@@ -118,6 +121,7 @@
                 }

     nodeDragStarted: (d, i) ->
+        d3.event.preventDefault
         d3.event.sourceEvent.stopPropagation()
         d3.select(@).classed "dragging", true
         d.fixed = true


Fix up new jasmine tests

Fix up new jasmine tests

--- common/lib/xmodule/xmodule/js/spec/combinedopenended/display_spec.coffee
+++ common/lib/xmodule/xmodule/js/spec/combinedopenended/display_spec.coffee
@@ -1,16 +1,15 @@
-xdescribe 'CombinedOpenEnded', ->
+describe 'CombinedOpenEnded', ->
   beforeEach ->
     spyOn Logger, 'log'
     # load up some fixtures
     loadFixtures 'combined-open-ended.html'
     @element = $('.combined-open-ended')

-    describe 'constructor', ->
-      beforeEach ->
-        @combined = new CombinedOpenEnded @element

-      it 'set the element', ->
-        except(@combined.element).not.toEqual @element
+  describe 'constructor', ->
+    beforeEach ->
+      @combined = new CombinedOpenEnded @element
+    it 'set the element', ->
+      expect(@combined.element).toEqual @element

-      #it 'initialize the ajax url, state, and task count', ->


Removed session support, needs to improved for UI/UX.

--- client/app/CommonViews/applicationview/applicationtabview.coffee
+++ client/app/CommonViews/applicationview/applicationtabview.coffee
@@ -11,7 +11,8 @@

     super options, data

-    @isSessionEnabled = options.saveSession and options.sessionName
+    # TODO: fatihacet: session logic should refactored to ui and ux
+    @isSessionEnabled = no #options.saveSession and options.sessionName

     appView = @getDelegate()


Fetch them one by one

Fetch them one by one

--- client/app/CommonViews/comments/commentlistviewcontroller.coffee
+++ client/app/CommonViews/comments/commentlistviewcontroller.coffee
@@ -49,10 +49,10 @@
         listView.emit "BackgroundActivityStarted"
         callback()

-      for xto in [0..repliesCount] by 10
+      for xto in [repliesCount..0]
         do (xto) =>
           stack.push (callback) =>
-            @fetchCommentsByRange xto, xto+10, (err, comments) =>
+            @fetchCommentsByRange xto, xto+1, (err, comments) =>
               if not removedAlready
                 @removeAllItems()
                 removedAlready = yes


Herbarium examples

Herbarium examples

--- content/herbarium-examples.coffee
+++ content/herbarium-examples.coffee
@@ -15,21 +15,24 @@
   },{
     label: "Specimen label"
     content: '''
-                  Transcribe the label information. There may be more than one label per specimen on the sheet. Choose the correct label as shown here and click on the top left hand corner and draw a rectangle around the label. There may be more than one label per specimen on the sheet.
+                  Select the label.
                  '''
     figures: [{
       label: '''
-                   Choose the hand written or typed label giving the details of the species name, date and location details. This is usually in the bottom right hand corner of the herbarium sheet. Where there are multiple collections from different locations mounted on the same sheet, the barcode associated to the correct specimen will normally be positioned nearest to it.
+                   There may be more than one label per specimen on the sheet. Choose the correct label as shown here, giving the species name, date and location. This is usually in the bottom right hand corner of the herbarium sheet. Where there are multiple collections from different locations mounted on the same sheet, the barcode associated to the correct specimen will normally be positioned nearest to it.  Click and drag to draw a rectangle around the label.
                    '''

       image: "examples/label1.jpg"
       alt: "Herbarium sheet with the label highlighted."
     },{
       label: '''
-
Verify that the species name (scientific name: genus and species – explain more) matches the label, or if no species name appears in this field, please transcribe the species name verbatim as it appears on the label.
-                   Please note: Historical herbarium sheet labels often record a former name or names for a specimen which differ from the currently accepted name.

+
Check and transcribe the label.

+
If no species name appears in the species field, please transcribe the species name verbatim as it appears on the label.
+                   Please note: Historical herbarium sheet labels often record a former name or names for a specimen which differ from the currently accepted name.

Check that the date matches the label, if no date appears in the date field please enter the date in the format dd/mm/yyyy.  If more than one date appears on the label (or any other label on the sheet) we only want the specimen collection date recorded (other dates may refer to when the specimen was acquired by a herbarium, or when the specimen was determined or re-determined at a later date).

+
+
Check that the location matches the label. Correct it if necessary, or enter it if it has not been transcribed already.

                    '''


Fix onboarding seed confirmation view controller

--- app/src/controllers/onboarding/management/onboarding_management_seed_confirmation_view_controller.coffee
+++ app/src/controllers/onboarding/management/onboarding_management_seed_confirmation_view_controller.coffee
@@ -50,7 +50,7 @@
       word = document.createElement("div")
       word.className = 'seed-word'
       span = document.createElement("span")
-      span.innerHTML = (i + 1) + '.'
+      span.innerHTML = if @params.swapped_bip39 then String.fromCharCode(0x41 + i) + '.' else (i + 1) + '.'
       word.appendChild(span)
       div = document.createElement("div")
       word.appendChild(div)


Test `getInviteCount`

Test `getInviteCount`

--- services/web/test/UnitTests/coffee/Collaborators/CollaboratorsInviteHandlerTests.coffee
+++ services/web/test/UnitTests/coffee/Collaborators/CollaboratorsInviteHandlerTests.coffee
@@ -20,6 +20,7 @@
 			@findOne: sinon.stub()
 			@find: sinon.stub()
 			@remove: sinon.stub()
+			@count: sinon.stub()
 		@Crypto = Crypto
 		@CollaboratorsInviteHandler = SandboxedModule.require modulePath, requires:
 			'settings-sharelatex': @settings = {}
@@ -48,6 +49,34 @@
 			privileges:     @privileges
 			createdAt:      new Date()

+	describe 'getInviteCount', ->
+
+		beforeEach ->
+			@ProjectInvite.count.callsArgWith(1, null, 2)
+			@call = (callback) =>
+				@CollaboratorsInviteHandler.getInviteCount @projectId, callback
+
+		it 'should not produce an error', (done) ->
+			@call (err, invites) =>
+				expect(err).to.not.be.instanceof Error
+				expect(err).to.be.oneOf [null, undefined]
+				done()
+
+		it 'should produce the count of documents', (done) ->
+			@call (err, count) =>
+				expect(count).to.equal 2
+				done()
+
+		describe 'when model.count produces an error', ->
+
+			beforeEach ->
+				@ProjectInvite.count.callsArgWith(1, new Error('woops'))
+
+			it 'should produce an error', (done) ->
+				@call (err, count) =>
+					expect(err).to.be.instanceof Error
+					done()
+
 	describe 'getAllInvites', ->

 		beforeEach ->


[grunt] better comments for `grunt-nunjucks-2-html` task

--- tasks/grunt-nunjucks-2-html.coffee
+++ tasks/grunt-nunjucks-2-html.coffee
@@ -5,6 +5,10 @@
 ###

 module.exports = (grunt) ->
+  # ======
+  # Config
+  # ======
+
   taskConfig =
     autoescape          : false
     data:
@@ -38,6 +42,10 @@
       trim              : true
       failureOutput     : 'non-printable-url'

+  # ====================
+  # Requires and caching
+  # ====================
+
   _            = require('lodash')
   path         = require('path')
   numbro       = require('numbro')
@@ -67,6 +75,9 @@
   layoutsDir    = taskConfig.path.layouts
   localesDir    = taskConfig.path.locales

+  # =======
+  # Helpers
+  # =======

   ###*
    * Replace placeholders with provided values via `sprintf` or `vsprintf`. Function will choice
@@ -102,7 +113,11 @@
       pagepath = pagepath.replace(_basename + _ext, _basename + '/index' + _ext)
     pagepath

-  # Output or not locale's dir name based on whether it's base locale or not.
+  ###*
+   * Output or not locale's dirname based on whether it's base locale or not
+   * @param  {string} locale Locale name for which should be made resolving
+   * @return {string} Directory name, in which resides build for specified locale
+  ###
   getLocaleDir = (locale) ->
     _baseUrl = _.find(taskConfig.i18n.locales, { locale: locale }).url
     _url = if _baseUrl then _baseUrl else locale
@@ -121,7 +136,9 @@
     # for other — only first part
     if _matched[3] then _matched[1] + '-' + _matched[2] else _matched[1]

-  # Load and invoke content of l10n files
+  # ===============
+  # Load l10n files
+  # ===============
   # @note In native `xgettext` you can set as many domains as you wish per locale. Usually domains
   #       represented in form of separate l10n files per singe locale. However, `node-gettext` using domains
   #       for holding locales and switching between them. To workaround that issue, following code will
@@ -143,8 +160,10 @@

       i18n.addTextdomain(domain, messages)

-
-  # Construct task
+  # ====================
+  # Construct Grunt Task
+  # ====================
+
   Task = ->
     # Define targets, with unique options and files, for each locale
     locales.forEach (currentLocale) =>


improve caller display for internal JS calls

--- app/assets/javascripts/connections.js.coffee
+++ app/assets/javascripts/connections.js.coffee
@@ -15,7 +15,11 @@

     if call.incoming
       env.callDialogActive = true
-      name = app.getAgentFrom(call.visibleNameCaller)
+      # FIXME When agents call themselves internally, the
+      #       "caller" can appear as "asterisk":
+      #
+      name = app.getAgentFrom(call.caller)
+      name = app.getAgentFrom(call.visibleNameCaller) if name == 'asterisk'
       app.takeIncomingCall(call, name)

   phone.notifyRemoveCall = (call) ->


Added some default values to users.

Added some default values to users.

--- app/assets/javascripts/models/user.js.coffee
+++ app/assets/javascripts/models/user.js.coffee
@@ -1,4 +1,12 @@
 class Cloudsdale.Models.User extends Backbone.Model

   url: -> "/v1/users/#{@id}.json"
-  +
+  defaults:
+    name: ""
+    avatar: {}
+    email: ""
+    time_zone: ""
+    role: "normal"
+    member_since: null
+    invisible: false


:lipstick:

:lipstick:

--- app/assets/javascripts/models/user.js.coffee
+++ app/assets/javascripts/models/user.js.coffee
@@ -19,4 +19,4 @@
     @rankings().where(questionId: question.id())

   voteForQuestion: (question) ->
-    @votes().find(questionId: question.id())+    @votes().find(questionId: question.id())


server - more time for GA to recognize auth

--- server/googleAnalytics.coffee
+++ server/googleAnalytics.coffee
@@ -37,7 +37,8 @@
         reject new Error "[ERROR] OAuth error; err = #{ err }"
       else
         console.info "[SUCCESS] server OAuthed w/ GA."
-        gapi.discover('analytics', 'v3').withAuthClient(authClient).execute (err, client) ->
+
+        discover = gapi.discover('analytics', 'v3').withAuthClient(authClient).execute (err, client) ->
           if err
             reject new Error "[ERROR] gapi.discover.execute, err = #{ err }"
           else
@@ -46,6 +47,8 @@
             serviceClient.expireAt = moment().add('days', 1).startOf('day').unix()
             resolve()
           return
+
+        setTimeout discover, 1000
       return
   return


pref work

pref work

--- app/assets/javascripts/preferences.js.coffee
+++ app/assets/javascripts/preferences.js.coffee
@@ -56,19 +56,13 @@
   pref = new Preferences
   $('#t_morning').click =>
     pref.setClassTime('morning')
+    $('#time_choice').html("Thanks! We'll remember that you're a morning person in mind when we plan your schedule.")
   $('#t_afternoon').click =>
     pref.setClassTime('afternoon')
+    $('#time_choice').html("Thanks! We'll remember that you're a afternoon person in mind when we plan your schedule.")
   $('#t_evening').click =>
+    $('#time_choice').html("Thanks! We'll remember that you're a evening person in mind when we plan your schedule.")
     pref.setClassTime('evening')
-
-  $('#choose_morning').click =>
-    $('#time_choice').html("Thanks! We'll remember that you're a morning person in mind when we plan your schedule.")
-
-  $('#choose_afternoon').click =>
-    $('#time_choice').html("Thanks! We'll remember that you're a afternoon person in mind when we plan your schedule.")
-
-  $('#choose_evening').click =>
-    $('#time_choice').html("Thanks! We'll remember that you're a evening person in mind when we plan your schedule.")

   state = 1
   change_state(state)


Allow comments of any non-zero length

Allow comments of any non-zero length

--- templates/embed/CommentForm.coffee
+++ templates/embed/CommentForm.coffee
@@ -40,7 +40,7 @@
       @showCommentForm()

   hasBody: ->
-    @body().replace(/\s/g, '').length > 4
+    @body().replace(/\s/g, '').length > 0

   showCommentForm: ->
     @element.querySelector('a').style.display = 'none'


Fix submit after upload file on s3 for IE8

--- app/assets/javascripts/s3_form_for.js.coffee
+++ app/assets/javascripts/s3_form_for.js.coffee
@@ -279,10 +279,16 @@
       console.log('s3_upload_complete 5')
       $("#upload_s3_path").val(content.filepath)
       console.log('s3_upload_complete 6')
-      console.log(selectors)
+      console.log('start submit')
       console.log(selectors.s3_form)
-      $(selectors.s3_form).submit()
-      console.log('s3_upload_complete end')
+      console.log($("#{selectors.s3_form} #form_submit").length)
+      $("#{selectors.s3_form} #form_submit").submit()
+#      Hack for IE9
+#      $("#{selectors.s3_form} #form_submit").submit (e) ->
+#        e.preventDefault();
+#        $(this).closest("form").submit()
+
+      $(selectors.s3_form).trigger('submit')

   build_content_object = ($uploadForm, file, result) ->
     console.log('zzzzzzzzzzzzzzzzzzzz')


Remove useless property on attachments drag and drop settings.

--- app/coffee/modules/common/attachments.coffee
+++ app/coffee/modules/common/attachments.coffee
@@ -208,7 +208,6 @@
             items: "div.single-attachment"
             handle: "a.settings.icon.icon-drag-v"
             containment: ".attachments"
-            cursorAt: {right: 50}
             dropOnEmpty: true
             scroll: false
             tolerance: "pointer"


Add action on focus comment textarea

Add action on focus comment textarea

--- app/coffee/modules/userstories/detail.coffee
+++ app/coffee/modules/userstories/detail.coffee
@@ -170,6 +170,9 @@
                 $confirm.notify("error")

             $tgrepo.save($scope.us).then(onSuccess, onError)
+
+        $el.on "focus", ".add-comment textarea", (event) ->
+            $(this).addClass('active')

         $el.on "click", ".us-activity-tabs li a", (event) ->
             $el.find(".us-activity-tabs li a").toggleClass("active")


Test cleanup

Test cleanup

--- source/javascripts/suite.js.coffee
+++ source/javascripts/suite.js.coffee
@@ -242,7 +242,7 @@
     })
     tester.verifyVisible()

-  test 'Assign different animation class', ->
+  test 'assign different animation class', ->
     tester.init({
       animation: 'pop-down'
     }).click()
@@ -250,12 +250,12 @@

   test 'data attribute support', ->
     $('.link_trigger a')
-      .attr('data-wdz-delegate', 'true')
+      .attr('data-wdz-delegate', 'a.bar')
       .attr('data-wdz-init_shown', 'true')
       .attr('data-wdz-container', '#foo')
       .attr('data-wdz-animation', 'pop-down')
     wdz = tester.init().data('windoze')
-    equal wdz.delegate, true, 'delegate is set to true'
+    equal wdz.delegate, 'a.bar', 'delegate is set to a.bar'
     equal wdz.init_shown, true, 'init_shown is set to true'
     equal wdz.container, '#foo', 'container is set to #foo'
     equal wdz.animation, 'pop-down', 'animation is set to pop-down'


Ensure options is always an object on create

--- src/scripts/models/contents/collection.coffee
+++ src/scripts/models/contents/collection.coffee
@@ -49,10 +49,9 @@
     # Proxy Backbone.Collection methods to make this model also work like a Collection
     #

-    add: () ->
-      @get('contents').add(arguments...)
+    add: () -> @get('contents').add(arguments...)

-    create: (models, options) ->
+    create: (models, options = {}) ->
       options.xhrFields =
         withCredentials: true
       options.wait = true # Always wait for a server response before adding the model to the collection


chm script displays date

chm script displays date

--- src/scripts/chm.coffee
+++ src/scripts/chm.coffee
@@ -12,11 +12,14 @@
         handler = new HtmlParser.DefaultHandler()
         parser  = new HtmlParser.Parser handler
         parser.parseComplete body
-        result = Select handler.dom, ".tdihevent p"
-        return unless result
-        title = trim result[0].children[0].raw
-        blurb = trim result[1].children[0].raw
+        dateEl = Select handler.dom, ".title"
+        contentEl = Select handler.dom, ".tdihevent p"
+        return unless dateEl and contentEl
+        title = trim contentEl[0].children[0].raw
+        blurb = trim contentEl[1].children[0].raw
         sentences = blurb.split('.')
+        date = trim dateEl[0].children[0].raw
+        msg.send date
         msg.send title
         for sentence in sentences
           msg.send sentence + '.' if sentence and sentence isnt ""


Add missing path require

Add missing path require

--- spec/package-generator-spec.coffee
+++ spec/package-generator-spec.coffee
@@ -1,5 +1,6 @@
 RootView = require 'root-view'
 fsUtils = require 'fs-utils'
+path = require 'path'

 describe 'Package Generator', ->
   [packageGenerator] = []