Hotfix para alterar coisas externas quando sku é selecionado

--- src/coffee/portal-sku-selector.coffee
+++ src/coffee/portal-sku-selector.coffee
@@ -107,7 +107,7 @@
 		@data.image = @data.skus[0].image

 		# Build selectors from given select strings.
-		@select = _.mapObj options.selectors, (key, val) =>
+		@select = _.mapObj @options.selectors, (key, val) =>
 			( => $(val, @context) )

 		@select.inputs = => $('input, select', @context)
@@ -324,7 +324,7 @@
 		# Trigger event for interested scripts
 		$this.trigger 'vtex.sku.dimensionChanged', [dimensionName, dimensionValue]
 		if selectableSkus.length == 1
-			$this.trigger 'vtex.sku.selected', [selectableSkus[0]]
+			$this.trigger 'vtex.sku.selected', [selectableSkus[0], productData]

 	# Handles submission in the warn unavailable form
@@ -420,7 +420,20 @@
 #
 # EVENTS
 #
-$(document).on "vtex.sku.selected", (evt, sku) ->
+$(document).on "vtex.sku.selected", (evt, sku, productData) ->
 	window.FireSkuChangeImage?(sku.sku)
 	#window.FireSkuDataReceived?(sku.sku)
 	window.FireSkuSelectionChanged?(sku.sku)
+
+	$('.buy-button').attr('href', $.skuSelector.getAddUrlForSku(sku.sku, sku.sellerId, 1, productData.salesChannel))
+
+	listPrice = _.formatCurrency sku.listPrice/100
+	bestPrice = _.formatCurrency sku.bestPrice/100
+	installments = sku.installments
+	installmentValue = _.formatCurrency sku.installmentsValue/100
+
+	$('.skuListPrice').text("R$ #{listPrice}")
+	$('.skuBestPrice').text("R$ #{bestPrice}")
+	if installments > 1
+		$('.skuBestInstallmentValue').text("R$ #{installmentValue}")
+		$('.skuBestInstallmentNumber').text(installments)


Typo.

Typo.

--- src/coffee/sync/inventory-sync.coffee
+++ src/coffee/sync/inventory-sync.coffee
@@ -35,7 +35,7 @@
           a.expectedDelivery = diff.expectedDelivery[0]
         else if size is 2
           a.expectedDelivery = diff.expectedDelivery[1]
-        # Delete case (size is 3) - we don not set any expectedDelivery
+        # Delete case (size is 3) - we do not set any expectedDelivery
         actions.push a
     return actions


Another way to fix focus of webview

Another way to fix focus of webview

--- views/services/layout.coffee
+++ views/services/layout.coffee
@@ -42,7 +42,6 @@
     factor = Math.max(window.webviewWidth / 800.0 * 100 / 100.0, 0.00125)
   window.webviewFactor = factor

-  webviewBounds = webview?.getBoundingClientRect() || {}
   # Autoset style
   if window.layout == 'horizontal'
     tabpaneHeight = "#{window.innerHeight / window.zoomLevel - poiControlHeight}px"
@@ -71,14 +70,6 @@
     poi-nav poi-nav-tabs .nav .dropdown-menu {
       max-height: #{tabpaneHeight};
       overflow: auto;
-    }
-    .webview-focus-area {
-      top: #{webviewBounds.top}px;
-      left: #{webviewBounds.left}px;
-      width: #{webviewBounds.width}px;
-      height: #{webviewBounds.height}px;
-      position: absolute;
-      pointer-events: none;
     }
     """
   # Resize when window size smaller than webview size
@@ -234,10 +225,10 @@
     titleBarArea.style["pointer-events"] = "none";
     document.body.appendChild(titleBarArea);
   # Workaround for webview focus area
-  webviewFocusArea = document.createElement("div");
-  webviewFocusArea.className = "webview-focus-area"
-  webviewFocusArea.onclick = () => $('webview').focus();
-  document.body.appendChild(webviewFocusArea);
+  $('poi-main').addEventListener 'touchstart', (e)->
+    if e.target == $('webview')
+      $('webview').focus()
+
   $('kan-game webview').setAudioMuted(true) if config.get 'poi.content.muted', false
   $('kan-game webview').loadURL config.get 'poi.homepage', 'http://www.dmm.com/netgame/social/application/-/detail/=/app_id=854854/'
   $('kan-game webview').addEventListener 'page-title-set', handleTitleSet


Refactor and comment client App.

Refactor and comment client App.

--- app/client/js/app.coffee
+++ app/client/js/app.coffee
@@ -8,28 +8,45 @@
     @param {app.react.App} reactApp
     @param {Element} element
     @param {app.Title} appTitle
+    @param {app.songs.Store} songsStore
     @constructor
   ###
-  constructor: (router, routes, reactApp, element, appTitle) ->
+  constructor: (router, routes, reactApp, element, appTitle,
+      songsStore) ->

-    syncView = ->
-      if @component
-        @component.forceUpdate()
-      else
-        @component = React.renderComponent reactApp.create(), element
-      document.title = appTitle.get()
-
-    onRouterError = (e) ->
-      # NOTE(steida): Here we can handle various errors.
-      # For error from server, say something like "Try it later.",
-      # for client error, log them then reload browser probably.
+    routes.addToEste router
+    router.listen 'error', (e) ->
+      # PATTERN(steida): Here we can handle various errors.
+      # For server error, alert something like "Try it later...".
+      # For client error, log it.
       # We can decide by: e.reason
       alert e.reason
-      # NOTE(steida): App can be in wrong state, so reload it.
-      forceReloadToEnsureFreshScripts = true
-      location.reload forceReloadToEnsureFreshScripts
+      # TODO(steida): For client-side error, show popup link to reload app.
+      # Don't reload automatically since it can cause loop.

-    routes.addToEste  router
-    routes.listen este.Routes.EventType.CHANGE, syncView
-    router.listen este.Router.EventType.ERROR, onRouterError
+    # PATTERN(steida): Listen all listenables representing app state here.
+    # Don't worry about performance, all data should be fetched already.
+    # Only data supposed to be shown are processed with React DOM diff.
+    for listenable in [
+      # TODO(steida): Check, what if store dispatches before routes.
+      routes
+      songsStore
+    ]
+      listenable.listen 'change', ->
+        document.title = appTitle.get()
+        if !@component
+          @component = React.renderComponent reactApp.create(), element
+          return
+
+        # PATTERN(steida): Since React component is already rendered, all we
+        # need is forceUpdate. Remember this article?
+        # http://swannodette.github.io/2013/12/17/the-future-of-javascript-mvcs/
+        # Om is using props, so it needs to deal with http://facebook.github.io/react/docs/component-specs.html#updating-shouldcomponentupdate.
+        # Este.js does not use React props nor state, because app state is
+        # stored in app stores. Therefore we can update DOM via forceUpdate
+        # super fastly, because forceUpdate skips shouldComponentUpdate method.
+        # React props and state are nice and all, but for Este.js, it's better
+        # to store app state out of React components, because isomorphic-ness.
+        @component.forceUpdate()
+
     router.start()


Refactored Twitch.coffee

Refactored Twitch.coffee

--- twitch.coffee
+++ twitch.coffee
@@ -1,5 +1,5 @@
 # Description:
-#   Twitch API - Find live streams
+#   Twitch Public API
 #
 # Dependencies:
 #   None
@@ -8,67 +8,60 @@
 #   None
 #
 # Commands:
-#   hubot ttv <category> - Perform a case-sensitive search on Twitch.tv for live streams in a game category (returns the first 5)
-#   hubot ttv featured - Return the first 5 featured live streams on Twitch.tv
+#   hubot ttv game <category> - Performs a case-sensitive search on Twitch.tv for live streams in a game <category> (returns the first 5)
+#   hubot ttv featured - Returns the first 5 featured live streams on Twitch.tv
 #   hubot ttv stream <name> - Returns information about stream <name>
 #
 # Author:
 #   MrSaints
+#   mbwkarl
+#
+# Todo:
+# - Save favourites?
+#

 module.exports = (robot) ->
-    robot.respond /ttv (.*)/i, (msg) ->
-        tempv = msg.match[1].indexOf "stream",0
-        if msg.match[1] is "featured"
-            getStreams msg, true
-        else if tempv is 0
-            getStreams msg, false, true
-        else
-            getStreams msg
-
-
-getStreams = (msg, featured, channel) ->
-    category = msg.match[1]
-
-    API = "/streams"
-    type = "streams"
-
-    if featured
-        API = "/streams/featured"
-        type = "featured"
-
-    if channel
-        streamsplit = msg.match[1].split " "
-        API = "/streams/#{streamsplit[1]}"
-        type = "stream"
-
-
-    msg.http("https://api.twitch.tv/kraken#{API}")
-        .query
-            game: category
-            limit: 5
-        .get() (err, res, body) ->
-            data = JSON.parse body
-
-            if channel
-                if not data[type]
-                    msg.reply "The stream is either offline, or does not exist."
-                    return
-
-                if data[type]
-                    stream = data[type]
-                    msg.send "#{stream.channel.display_name} is streaming #{stream.channel.game} @ #{stream.channel.url}"
-                    msg.send "Stream status: \"#{stream.channel.status}\""
-                    msg.send "Viewers: #{stream.viewers}"
-                    return
-
-            if data._total is 0
-                msg.reply "No live streams were found in '#{category}'. Try a different category or try again later."
+    robot.respond /ttv game (.*)/i, (msg) ->
+        category = msg.match[1]
+        twitch_request msg, '/streams', { game: category, limit: 5 }, (object) ->
+            if object._total is 0
+                msg.reply "No live streams were found in \"#{category}\". Try a different category or try again later."
                 return

-            for stream in data[type][0..4]
-                stream = stream.stream if featured
-                msg.send "#{stream.channel.display_name} (#{stream.channel.status}) - #{stream.channel.url} [Viewers: #{stream.viewers}]"
+            for stream in object.streams
+                channel = stream.channel
+                msg.send "#{channel.display_name} (\"#{channel.status}\") - #{channel.url} [Viewers: #{stream.viewers}]"

-            if !featured and data._total > 5
-                msg.reply "There are #{data._total - 5} other '#{category}' live streams."
+            if object._total > 5
+                msg.reply "There are #{object._total - 5} other \"#{category}\" live streams."

+    robot.respond /ttv featured/i, (msg) ->
+        twitch_request msg, '/streams/featured', { limit: 5 }, (object) ->
+            for feature in object.featured
+                channel = feature.stream.channel
+                msg.send "#{feature.stream.game}: #{channel.display_name} (#{channel.status}) - #{channel.url} [Viewers: #{feature.stream.viewers}]"
+
+    robot.respond /ttv stream (.*)/i, (msg) ->
+        twitch_request msg, "/streams/#{msg.match[1]}", null, (object) ->
+            if object.status is 404
+                msg.reply "The stream you have entered (\"#{msg.match[1]}\") does not exist."
+                return
+
+            if not object.stream
+                msg.reply "The stream you have entered (\"#{msg.match[1]}\") is currently offline."
+                return
+
+            channel = object.stream.channel
+            msg.send "#{channel.display_name} is streaming #{channel.game} @ #{channel.url}"
+            msg.send "Stream status: \"#{channel.status}\""
+            msg.send "Viewers: #{object.stream.viewers}"
+
+twitch_request = (msg, api, params = {}, handler) ->
+    msg.http("https://api.twitch.tv/kraken#{api}")
+        .query(params)
+        .get() (err, res, body) ->
+            if err
+                msg.reply "An error occurred while attempting to process your request: #{err}"
+                return
+
+            handler JSON.parse(body)


test $watch

test $watch

--- src/index.coffee
+++ src/index.coffee
@@ -71,3 +71,8 @@
 console.log app.$children
 console.log app.$
 console.log app.$$
+
+# instance methods
+unwatch = app.$watch 'count', (newValue, oldValue) ->
+  console.log "$watch count: #{oldValue} -> #{newValue}"
+  unwatch() if newValue is 1


Fix typo

Fix typo

--- src/index.coffee
+++ src/index.coffee
@@ -16,10 +16,10 @@
     when 'break'
       $ 'br', key: key+'break'
     when 'inlineCode'
-      # TODO: code is valide?
+      # TODO: code is valid?
       $ 'code', {key: key+'inlineCode'}, node.value
     when 'code'
-      # TODO: code is valide?
+      # TODO: code is valid?
       $ 'code', {key: key+'code'}, node.value
     when 'heading'
       tag = 'h'+node.depth.toString()


testing

testing

--- src/index.coffee
+++ src/index.coffee
@@ -35,9 +35,11 @@

       if (moment().isBetween(meetingStartTime, meetingStartTime.add(1, 'minute')))
         @validUntil = meetingStartTime.add(1, 'minute')
+        console.log "Inside initial valid until block: #{@validUntil} and meetingStartTime: #{meetingStartTime}"

       if moment(@validUntil).isBefore(moment().utc())
         meetingId = _.get currentMeeting, 'meetingId'
+        console.log "Ending the meeting because : \nvalidUntil:#{validUntil} and current time: #{moment()}\n"
         @endMeeting meetingId

   endMeeting: (meetingId) =>


Export confmerge

Export confmerge

--- src/index.coffee
+++ src/index.coffee
@@ -3,5 +3,6 @@
 ###

 configure = require './configure'
+confmerge = require './confmerge'

-module.exports = { configure }
+module.exports = { configure, confmerge }


Only compile the deps bundle when deps are added because order is not maintained.

--- src/index.coffee
+++ src/index.coffee
@@ -87,11 +87,15 @@
     # Build the file bundle.
     fileBundle.bundle deps: deps, (err, fileContent) ->
       # Add any new dependencies in the cache to the dependency bundle.
+      added = false   # Keep track of whether we added any.
       for d in depsCache when d not in depsBundle.files
         # Expose the bundle with the absolute filename.
         depsBundle.require d, expose: d
         # Watch dependency files for changes if requested in the config.
         watch d if config.watch
+        added = true  # Set the added flag.
+      # Bail and write the file bundle unless new dependencies were added.
+      return done fileContent unless added
       # Write out the dependency bundle.
       writeDeps -> done fileContent


Make sure we flush the deferred tasks

Make sure we flush the deferred tasks

--- src/index.coffee
+++ src/index.coffee
@@ -326,6 +326,7 @@

     if @_options.flushDeferredTasks
       @log.debug "Flushing deferred tasks '#{@_deferredTaskMatches}'"
+      @_deferredTaskMatches = []
       return callback()

     @log.info "Executing deferred tasks"


fixed Object.assigning a null value

fixed Object.assigning a null value

--- src/index.coffee
+++ src/index.coffee
@@ -182,7 +182,8 @@
         data[k] = opts[k]
       else
         for k2, v2 of data[k]
-          objectAssign data[k][k2], opts[k][k2]
+          if opts[k]? && opts[k][k2]?
+            objectAssign data[k][k2], opts[k][k2]

   @data.set data


send back registered methods for all parent frames

--- src/index.coffee
+++ src/index.coffee
@@ -112,10 +112,24 @@
             return localMethod method, params
           else
             @client.call method, params
+            .then (result) ->
+              # need to send back methods for all parent frames
+              if method is 'ping'
+                localResult = localMethod method, params
+                (result or []).concat localResult
+              else
+                result
             .catch (err) =>
               parentError = err
               if @sw
                 @sw.call method, params
+                .then (result) ->
+                  # need to send back methods for all parent frames
+                  if method is 'ping'
+                    localResult = localMethod method, params
+                    (result or []).concat localResult
+                  else
+                    result
                 .catch ->
                   return localMethod method, params
               else


Missed a line to comment out

Missed a line to comment out

--- app/modules/shape.coffee
+++ app/modules/shape.coffee
@@ -1,4 +1,4 @@
 module.exports = class Shape
   constructor: (@x, @y, @width, @height, @start, @end, @id) ->
     #console.log('%s x:%s y:%s w:%s h:%s s:%s e:%s',
-      @id, @x, @y, @width, @height, @start, @end)
+    #  @id, @x, @y, @width, @height, @start, @end)


Add a limit

Add a limit

--- app/pages/lab/media.cjsx
+++ app/pages/lab/media.cjsx
@@ -4,6 +4,7 @@
 apiClient = require '../../api/client'
 putFile = require '../../lib/put-file'

+MAX_MEDIA_COUNT = 20
 MAX_MEDIA_SIZE = 500000

 # For use on click.
@@ -140,8 +141,15 @@

-        Add an image

-        <MediaUploadArea project={@props.project} onUpload={@handleChange} />
+        <PromiseRenderer promise={@getMedia} then={(media) =>
+          if media.length < MAX_MEDIA_COUNT
+

+              Add an image

+              <MediaUploadArea project={@props.project} onUpload={@handleChange} />
+

+          else
+
You’ve reached the limit of {MAX_MEDIA_COUNT} images for this project. Delete some images to add new ones.

+        } />


Corrected Integer check

Corrected Integer check

--- src/is-an.coffee
+++ src/is-an.coffee
@@ -61,7 +61,7 @@

 isAn.Number.Literal.Infinite = (x) -> x == Infinity or x == -Infinity

-isAn.Number.Integer = (x) -> isAn.Number.Finite(x) and Math.floor(x) == x
+isAn.Number.Integer = (x) -> isAn.Number.Finite(x) and Math.floor(x) == Number(x)

 isAn.String = (x) -> toString(x) == '[object String]'


lexer: ignored char escapes in strings

lexer: ignored char escapes in strings


Fix for implicit if/try

Fix for implicit if/try

--- src/lexer.coffee
+++ src/lexer.coffee
@@ -521,7 +521,7 @@
             return
           if ttt in [ 'FINALLY' ]
             break
-      if ttt in [ '\n', ';', 'INDENT', '->', '=>', '@>' ]
+      if ttt in [ 'TERMINATOR', 'INDENT', '->', '=>', '@>' ]
           break
       else if ttt in [ ')', 'CALL_END', ']', '}', 'PARAM_END' ]
         stack.push tok


Show warning when more then 20 links need to be opened

--- src/links.coffee
+++ src/links.coffee
@@ -18,7 +18,9 @@
 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 # THE SOFTWARE.

-filters = {
+LINK_WARNING_AMOUNT = 20
+
+FILTERS = {
 						all: ".*",
 						image: "\.(jpg|jepg|png|gif|svg)$",
 						video: "\.(mov|qt|swf|flv|mpg|mpe|mpeg|mp2v|m2v|m2s|avi|asf|asx|wmv|wma|wmx|rm|ra|ram|rmvb|mp4|3g2|3gp|ogm|mkv)$",
@@ -26,10 +28,11 @@
 					}

 onClickHandler = (info, tab) ->
-	filter = filters[info.menuItemId]
+	filter = FILTERS[info.menuItemId]
 	chrome.tabs.sendMessage(tab.id, "getSelectedLinks", (response) ->
-		for link in response.links.reverse() when link.match(new RegExp(filter, "i"))
-			chrome.tabs.create({url:link,index:tab.index+1})
+		if response.links.length <= LINK_WARNING_AMOUNT or confirm("You have #{response.links.length} links to open. Are you sure you want to open them all at once?")
+			for link in response.links.reverse() when link.match(new RegExp(filter, "i"))
+				chrome.tabs.create({url:link,index:tab.index+1})
 	)

 onInstalledHandler = () ->


csl field extraction for #260

csl field extraction for #260

--- resource/translators/translator.coffee
+++ resource/translators/translator.coffee
@@ -271,7 +271,8 @@

     fields = []
     for own name, value of Translator.extractFields(@item)
-      switch name.toLowerCase()
+      name = name.toLowerCase()
+      switch name
         when 'mr'
           fields.push({ name: 'mrnumber', value: value.value })
         when 'zbl'


.

Former-commit-id: 4143aa2594f1b52493845f2e65207c6ebe6d2c27 [formerly df23d2e81efe0f60861b9c551397865d031f13c6] [formerly cf07713d1091a4d43699beda2102f198c745ba8f [formerly d89ecca469b0f8a695e145d27144371053c61a57]] [formerly 46d72cecd2cfba4c483fcb96cd0943b80cee15f6 [formerly 04a7a8215ff67742f6d2ba61dcc785931caeb306] [formerly 78308ec8995ca3ef88de82f018aa2ce2adcc0f05 [formerly 78308ec8995ca3ef88de82f018aa2ce2adcc0f05 [formerly 485e7d852c7ef1c1332721ab0817780c81ce4b68]]]]
Former-commit-id: 6a79784d02630e4fe9cf2208406307b223298712 [formerly 47b58f4b0c9615afeb4ab491cf28cdaddd6e9791] [formerly 5cfce5e87365115851e2ccdcde8bc888f7baa273 [formerly 1435af6d15e6f1e47a859fd6e32ee1701b088ecc]]
Former-commit-id: fa0069f9cf5c0263738adb97236457611453b8e6 [formerly 9a150d9f94a5fded75de35dab412121233a57944]
Former-commit-id: d9280e3964d51648e2da5e6512db584982357bb5
Former-commit-id: 177bc5e19d5312abd67bd4a87389187e118e3d21

--- @client/shared.coffee
+++ @client/shared.coffee
@@ -117,7 +117,7 @@
     skip_element = (behind_el && $(behind_el).has($(el)).length > 0) ||
                     (!is_image && rgb.a == 0)

-    console.log 'checking ', el, skip_element, is_image
+    console.log skip_element, is_image or is_image?, el.style['background-image'], el

     if skip_element
       hidden_els.push [el, el.style.visibility]


Removed hack that is no longer needed

Removed hack that is no longer needed

because animators don’t depend on time input anymore

--- framer/Animators/SpringRK4Animator.coffee
+++ framer/Animators/SpringRK4Animator.coffee
@@ -10,7 +10,6 @@
 			friction: 25
 			velocity: 0
 			tolerance: 1 / 100
-			time: null # Hack

 		@_time = 0
 		@_value = 0


updating book link text

updating book link text

--- src/components/navbar/book-links.cjsx
+++ src/components/navbar/book-links.cjsx
@@ -15,13 +15,13 @@
     if course.book_pdf_url
       links.push(

-          Book PDF
+          Homework PDF

       )
     if course.webview_url
       links.push(

-          Webview
+          Online Book

       )


Fix bug with CampaignEditor level connection lines sometimes not showing up

--- app/views/play/CampaignView.coffee
+++ app/views/play/CampaignView.coffee
@@ -82,8 +82,9 @@
     if window.serverConfig.picoCTF
       @supermodel.addRequestResource(url: '/picoctf/problems', success: (@picoCTFProblems) =>).load()
     else
-      @sessions = @supermodel.loadCollection(new LevelSessionsCollection(), 'your_sessions', {cache: false}, 0).model
-      @listenToOnce @sessions, 'sync', @onSessionsLoaded
+      unless @editorMode
+        @sessions = @supermodel.loadCollection(new LevelSessionsCollection(), 'your_sessions', {cache: false}, 0).model
+        @listenToOnce @sessions, 'sync', @onSessionsLoaded
       unless @terrain
         @campaigns = @supermodel.loadCollection(new CampaignsCollection(), 'campaigns', null, 1).model
         @listenToOnce @campaigns, 'sync', @onCampaignsLoaded
@@ -194,7 +195,7 @@
     context.levelsCompleted = count.completed
     context.levelsTotal = count.total

-    @determineNextLevel context.levels if @sessions?.loaded
+    @determineNextLevel context.levels if @sessions?.loaded or @editorMode
     # put lower levels in last, so in the world map they layer over one another properly.
     context.levels = (_.sortBy context.levels, (l) -> l.position.y).reverse()
     @campaign.renderedLevels = context.levels if @campaign
@@ -226,7 +227,7 @@
       context.campaigns = {}
       for campaign in @campaigns.models when campaign.get('slug') isnt 'auditions'
         context.campaigns[campaign.get('slug')] = campaign
-        if @sessions.loaded
+        if @sessions?.loaded
           levels = _.values($.extend true, {}, campaign.get('levels') ? {})
           count = @countLevels levels
           campaign.levelsTotal = count.total
@@ -265,11 +266,7 @@
     @updateHero()
     unless window.currentModal or not @fullyRendered
       @highlightElement '.level.next', delay: 500, duration: 60000, rotation: 0, sides: ['top']
-      if @editorMode
-        for level in @campaign?.renderedLevels ? []
-          for nextLevelOriginal in level.nextLevels ? []
-            if nextLevel = _.find(@campaign.renderedLevels, original: nextLevelOriginal)
-              @createLine level.position, nextLevel.position
+      @createLines() if @editorMode
       @showLeaderboard @options.justBeatLevel?.get('slug') if @options.showLeaderboard# or true  # Testing
     @applyCampaignStyles()
     @testParticles()
@@ -409,6 +406,12 @@
     experienceScore = adultPoint + speedPoints  # 0-6 score of how likely we think they are to be experienced and ready for Kithgard Mastery
     return experienceScore

+  createLines: ->
+    for level in @campaign?.renderedLevels ? []
+      for nextLevelOriginal in level.nextLevels ? []
+        if nextLevel = _.find(@campaign.renderedLevels, original: nextLevelOriginal)
+          @createLine level.position, nextLevel.position
+
   createLine: (o1, o2) ->
     mapHeight = parseFloat($(".map").css("height"))
     mapWidth = parseFloat($(".map").css("width"))


game state

game state

--- src/game_state_single_camera.coffee
+++ src/game_state_single_camera.coffee
@@ -3,12 +3,12 @@
 Its transform can be adjusted to view different areas and provide various camera effects.

 @name SingleCamera
-@fieldOf Engine
+@fieldOf GameState
 @module
 @param {Object} I Instance variables
 @param {Object} self Reference to the engine
 ###
-Engine.SingleCamera = (I, self) ->
+GameState.SingleCamera = (I, self) ->
   # Set some default properties
   Object.reverseMerge I,
     cameraTransform: Matrix.IDENTITY


<debug> Controller - Using correct method for update.

--- server/controllers/actuatorRule.coffee
+++ server/controllers/actuatorRule.coffee
@@ -29,7 +29,7 @@
 module.exports.update = (req, res) ->
 	# @todo Prevent from updating ruleId and sensorId (or verify consistency of new ones)
 	data = req.body
-	req.actuatorRule.update data, (err, actuatorRule) ->
+	req.actuatorRule.updateAttributes data, (err, actuatorRule) ->
 		if err
 			res.send error: err, 500
 		else


Add calculation index to child cases.

Add calculation index to child cases.

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

--- src/code/stores/simulation-store.coffee
+++ src/code/stores/simulation-store.coffee
@@ -39,6 +39,7 @@

     @nodes = []
     @currentSimulation = null
+    @experimentFrameIndex = 0

     @settings =
       simulationPanelExpanded: false
@@ -125,14 +126,11 @@
         onFrames: (frames) =>
           SimulationActions.simulationFramesCreated(frames)
           if @settings.isRecording
-            if @settings.isRecordingPeriod
-              SimulationActions.recordingFramesCreated(frames)
-            else
-              # Strip out the time information if we are simulating without time...
-              framesNoTime = _.map frames, (f) ->
-                # time: (removed)
-                nodes: f.nodes
-              SimulationActions.recordingFramesCreated(framesNoTime)
+            framesNoTime = _.map frames, (frame) =>
+              @experimentFrameIndex++
+              frame.time = @experimentFrameIndex
+              return frame
+            SimulationActions.recordingFramesCreated(framesNoTime)

         onStart: (nodeNames) =>
           SimulationActions.simulationStarted(nodeNames)


修改应用列表添加逻辑

修改应用列表添加逻辑

--- lib/rapid-dev/rapid-dev-mode-view.coffee
+++ lib/rapid-dev/rapid-dev-mode-view.coffee
@@ -35,7 +35,6 @@
     console.log @uri
     @projectInfos = {}

-    @addProjectListItem path for path in @findProject()
     @on 'click', '.settingsItem', (e) =>
       @menuClick(e.currentTarget)

@@ -44,6 +43,7 @@

   attached: ->
+    @addProjectListItem path for path in @findProject()
     console.log 'start'
     @toggleAddBtn()

@@ -72,6 +72,7 @@
         identifier: moduleConfig.identifier
         moduleName: moduleConfig.name
         modulePath: modulePath
+    console.log params
     Builder.activate(params);

   isProject: (path) ->
@@ -106,6 +107,7 @@
     return projects

   addProjectListItem: (path) ->
+    return if yes is @checkProjectExistInList path
     config = @readConfig path, 'project'
     if config?
       liStr = "
#{config.name}
"
@@ -149,7 +151,7 @@
         path = paths[0]
         console.log "select path:#{path}"
         if @isProject path
-          @addProjectListItem path if @checkProjectExistInList(path)  is no
+          @addProjectListItem path
         else
           alert Desc.selectCorrectProject


make selection-deletion applied in a single op, so undo works better

--- src/coffee/client/puzzle.coffee
+++ src/coffee/client/puzzle.coffee
@@ -237,10 +237,12 @@
                 row2 = Math.max(grid_focus.focus.row, grid_focus.anchor.row)
                 col1 = Math.min(grid_focus.focus.col, grid_focus.anchor.col)
                 col2 = Math.max(grid_focus.focus.col, grid_focus.anchor.col)
+                op = Ot.identity(@state.puzzle)
                 for row in [row1..row2]
                     for col in [col1..col2]
                         if g[row][col].open and g[row][col].contents != ""
-                            @props.requestOp Ot.opEditCellValue row, col, "contents", ""
+                            op = Ot.compose(@state.puzzle, op, Ot.opEditCellValue row, col, "contents", "")
+                @props.requestOp op

             @setState { grid_focus: grid_focus }


Docs: Added JSDocs for Object class.

Docs: Added JSDocs for Object class.

--- src/coffee/object/Object.coffee
+++ src/coffee/object/Object.coffee
@@ -10,7 +10,7 @@

   ###*
     @constructor
-    @param   {Object} options Class options
+    @param   {Object} options Class options.
     @param   {*} data Class data
     @extends {goog.events.EventTarget}
   ###
@@ -21,29 +21,113 @@
     @setOptions options  if options
     @setData data        if data

+    @setUid()
+
+
+  ###*
+    Sets options object of this class.
+
+    @param {!Object} options Options object.
+  ###
   setOptions: (options) ->
     @options = options

+
+  ###*
+    Returns all options object.
+
+    @return {Object} Options object.
+  ###
   getOptions: ->
     return @options

+
+  ###*
+    Returns value of a key in options object.
+
+    @return {*} Value of key or null.
+  ###
   getOption: (key) ->
     return @options[key] or null

+
+  ###*
+    Sets data of this class.
+
+    @param {*} data Data passed to class.
+  ###
   setData: (data) ->
     @data = data

+
+  ###*
+    Returns class data.
+
+    @return {*} Class data.
+  ###
   getData: ->
     return @data

+
+  ###*
+    Sets uid.
+  ###
+  setUid: ->
+    goog.getUid this
+
+
+  ###*
+    Return unique id set by closure.
+    This method is here for naming convension.
+
+    @return {number} Uid.
+  ###
+  getUid: ->
+    return goog.getUid this
+
+
+  ###*
+    Adds an event listener.
+    See also {@link goog.events.Listenable.prototype.listen}
+
+    @param {!string} eventName Name of the event.
+    @param {!Function} callback Callback function for the event.
+    @return {goog.events.ListenableKey} Unique key for the listener.
+  ###
   on: (eventName, callback) ->
-    goog.events.listen this, eventName, callback
+    return goog.events.listen this, eventName, callback

+
+  ###*
+    Adds an event listener that is removed automatically after the listener
+    fired once. See also {@link goog.events.Listenable.prototype.listenOnce}
+
+    @param {!string} eventName Name of the event.
+    @param {!Function} callback Callback function for the event.
+    @return {goog.events.ListenableKey} Unique key for the listener.
+  ###
   once: (eventName, callback) ->
-    goog.events.listenOnce this, eventName, callback
+    return goog.events.listenOnce this, eventName, callback

+
+  ###*
+    Dispatches an event and calls all listeners listening for events of this
+    event. See also {@link goog.events.Listenable.prototype.dispatchEvent}
+
+    @param {!string} eventName Name of the event.
+    @param {!Function} callback Callback function for the event.
+  ###
   emit: (eventName, data) ->
     @dispatchEvent { type: eventName, data }

+
+  ###*
+    Removes an event listener which was added with @on or @once.
+    See also {@link goog.events.Listenable.prototype.unlisten}
+
+    @param {!string} eventName Name of the event.
+    @param {!Function} callback Callback function for the event.
+    @return {Boolean} Whether any listener was removed.
+  ###
   off: (eventName, callback) ->
     return @unlisten eventName, callback


MER-41: Update import for auth config.

MER-41: Update import for auth config.

--- src/spec/product-import.spec.coffee
+++ src/spec/product-import.spec.coffee
@@ -1,9 +1,10 @@
 {ProductImport} = require '../lib'
+Config = require('../config')

 describe 'ProductImport', ->

   beforeEach ->
-    @import = new ProductImport
+    @import = new ProductImport null, Config

   it 'should initialize', ->
     expect(@import).toBeDefined()


dont need throttling

dont need throttling

--- src/concept-coach/launcher.cjsx
+++ src/concept-coach/launcher.cjsx
@@ -12,13 +12,11 @@
     isLaunching: false
     isClosing: false

-  toggleLaunching: ->
-    {isLaunching} = @state
-    @setState(isLaunching: not isLaunching)
-
-  toggleClosing: ->
-    {isClosing} = @state
-    @setState(isClosing: not isClosing)
+  launch: ->
+    @setState(isLaunching: true)
+
+  close: ->
+    @setState(isClosing: true)

   delayedReset: ->
     _.delay( =>
@@ -33,6 +31,9 @@
       channel.emit('launcher.clicked')
     , 1000)

+  shouldComponentUpdate: (nextProps, nextState) ->
+    @state.isClosing isnt nextState.isClosing or @state.isLaunching isnt nextState.isLaunching
+
   componentDidUpdate: ->
     {isLaunching, isClosing} = @state

@@ -42,12 +43,10 @@
       @delayedLaunch()

   componentWillMount: ->
-    @throttledLaunching = _.debounce @toggleLaunching, 1000, true
-    @throttledClosing = _.debounce @toggleClosing, 1000, true
-    channel.on 'coach.unmount.success', @throttledClosing
+    channel.on 'coach.unmount.success', @close

   componentWillUnmount: ->
-    channel.off 'coach.unmount.success', @throttledClosing
+    channel.off 'coach.unmount.success', @close

   render: ->
     {isLaunching, isClosing} = @state
@@ -56,7 +55,7 @@
       launching: isLaunching
       closing: isClosing

-

+

       <BS.Button bsStyle='primary'>Launch Concept Coach</BS.Button>
       <svg x="0px" y="0px" width="840px" height="388px" viewBox="0 0 840 388"
         version="1.1" xmlns="http://www.w3.org/2000/svg">


Added tries for initialization screenshots on server

--- lib/testing/screenshot/server.coffee
+++ lib/testing/screenshot/server.coffee
@@ -2,6 +2,7 @@

 fs = require 'fs-extra'
 pathUtils = require 'path'
+childProcess = require 'child_process'
 driver = require './driver'
 findWindow = require './server/findWindow'
 testScreenshot = require './server/testScreenshot'
@@ -12,6 +13,8 @@

 DEST = 'tests_results'
 INITIALIZATION_FILE_PATH = pathUtils.join DEST, 'initialization.png'
+INITIALIZATION_TRIES = 20
+INITIALIZATION_TRY_DELAY_SEC = 1

 fs.emptyDirSync DEST

@@ -66,8 +69,13 @@
         throw new Error "Screenshots on this platform are not supported"

     opts.path = INITIALIZATION_FILE_PATH
-    takeScreenshot opts
-    rect = findWindow opts
+    tryNo = 0
+    while not rect and tryNo++ < INITIALIZATION_TRIES
+        takeScreenshot opts
+        rect = findWindow opts
+        unless rect
+            log "Cannot initialize screenshots; try #{tryNo} / #{INITIALIZATION_TRIES}"
+        childProcess.execSync "sleep #{INITIALIZATION_TRY_DELAY_SEC}"
     unless rect
         throw new Error "Cannot find application on screenshot; check '#{opts.path}' file"
     rects[opts.clientUid] = rect


add browse the book tests for handling ecosystems

--- test/components/buttons/browse-the-book.spec.coffee
+++ test/components/buttons/browse-the-book.spec.coffee
@@ -52,9 +52,21 @@
     Testing.renderComponent( BTB, props: @props, routerParams: {courseId: undefined}).then ({dom}) ->
       expect(dom).to.be.null

-  it 'will pass down props like onClick', ->
-    console.info(@props)
-    Testing.renderComponent( BTB, props: @props ).then ({dom, element}) ->
+  it 'passes down props like onClick', ->
+    Testing.renderComponent( BTB, props: @props ).then ({element}) ->
       Testing.actions.click(element.getDOMNode())
+      expect(element.props.onClick).to.have.been.called

-      expect(@props.onClick).to.have.been.called()
+  it 'should append ecosystemId query string if props specify non-default ecosystemId', ->
+    @props.ecosystemId = '3'
+    Testing.renderComponent( BTB, props: @props ).then ({dom}) ->
+      expect(Testing.router.makeHref).to.have.been.calledWith(
+        'viewReferenceBookSection', { courseId: COURSE_ID, cnxId: undefined, section: [1.2] }, {ecosystemId: '3'}
+      )
+
+  it 'should ignore ecosystemId query string if props specify default ecosystemId', ->
+    @props.ecosystemId = '1'
+    Testing.renderComponent( BTB, props: @props ).then ({dom}) ->
+      expect(Testing.router.makeHref).to.have.been.calledWith(
+        'viewReferenceBookSection', { courseId: COURSE_ID, cnxId: undefined, section: [1.2] }
+      )


Fix busted watermark onload

Fix busted watermark onload

--- src/core/LiterallyCanvas.coffee
+++ src/core/LiterallyCanvas.coffee
@@ -60,7 +60,8 @@
         @repaintAllLayers()
     )

-    @watermarkImage.onload = => @repaintLayer('background')
+    if @watermarkImage
+      @watermarkImage.onload = => @repaintLayer('background')

   trigger: (name, data) ->
     @canvas.dispatchEvent(new CustomEvent(name, detail: data))


test controller error handling with Promise

--- tests/batman/controller/controller_test.coffee
+++ tests/batman/controller/controller_test.coffee
@@ -514,6 +514,34 @@
   deepEqual handlerSpy.lastCallArguments, [@error]
   deepEqual handlerSpy2.lastCallArguments, [@error]

+asyncTest '@handleError can be used in Promise::catch', 5,  ->
+  callbackSpy = createSpy()
+  handlerSpy = createSpy()
+  handlerSpy2 = createSpy()
+
+  @TestController::_customErrorHandler = handlerSpy
+  @TestController::_customErrorHandler2 = handlerSpy2
+  @TestController.catchError @CustomError, with: [@TestController::_customErrorHandler, @TestController::_customErrorHandler2]
+
+  makeAssertions = =>
+    equal callbackSpy.callCount, 0
+    equal handlerSpy.callCount, 1
+    equal handlerSpy2.callCount, 1
+    deepEqual handlerSpy.lastCallArguments, [@error]
+    deepEqual handlerSpy2.lastCallArguments, [@error]
+
+  namespace = @
+  controller = new @TestController
+  controller.index = ->
+    namespace.Model.load()
+      .then(callbackSpy)
+      .catch(@handleError)
+      .then(makeAssertions)
+      .then(QUnit.start())
+    @render false
+
+  controller.dispatch('index')
+
 test 'when wrapping a call with the errorHandler callback, any exception that is not tracked with specific catchError will be re-thrown', ->
   callbackSpy = createSpy()
   handlerSpy = createSpy()


Full unit test coverage with istanbul

Full unit test coverage with istanbul

Added missing coverage for _tick and _draw methods of Display

--- node/test/display/displayTest.coffee
+++ node/test/display/displayTest.coffee
@@ -261,12 +261,58 @@
         display._data["5,5"].should.eql [ 5, 5, '￠', '#ccc', '#000' ]

     describe "_tick", ->
-      xit "should be tested", ->
-        false.should.equal true
+      it 'should call RAF to reschedule itself', (done) ->
+        display = new ROT.Display()
+        prevRAF = global.requestAnimationFrame
+        global.requestAnimationFrame = ->
+          done()
+          global.requestAnimationFrame = prevRAF
+        display._tick()
+
+      it 'should return if nothing is dirty on the display', ->
+        display = new ROT.Display()
+        display._dirty = false
+        display._tick()
+
+      it 'should redraw everything if _dirty is true', (done) ->
+        display = new ROT.Display()
+        display._data =
+          "1,1": [ 1, 1, '@', '#fff', '#000' ]
+        display._dirty = true
+        display._draw = -> done()
+        display._tick()
+
+      it 'should redraw only the _dirty stuff', (done) ->
+        display = new ROT.Display()
+        display._data =
+          "1,1": [ 1, 1, '@', '#fff', '#000' ]
+          "2,2": [ 2, 2, '@', '#fff', '#000' ]
+          "3,3": [ 3, 3, '@', '#fff', '#000' ]
+        display._dirty =
+          "2,2": [ 2, 2, '@', '#fff', '#000' ]
+        display._draw = -> done()
+        display._tick()

     describe "_draw", ->
-      xit "should be tested", ->
-        false.should.equal true
+      it "should call draw on the backend", (done) ->
+        display = new ROT.Display()
+        display._backend =
+          draw: -> done()
+        display._data =
+          "1,1": [ 1, 1, '@', '#fff', '#000' ]
+          "2,2": [ 2, 2, '@', '#fff', '#000' ]
+          "3,3": [ 3, 3, '@', '#fff', '#000' ]
+        display._draw "2,2"
+
+      it "should set clearBefore if the background doesn't match", (done) ->
+        display = new ROT.Display()
+        display._backend =
+          draw: (data, clear) -> done() if clear
+        display._data =
+          "1,1": [ 1, 1, '@', '#fff', '#000' ]
+          "2,2": [ 2, 2, '@', '#fff', '#888' ]
+          "3,3": [ 3, 3, '@', '#fff', '#000' ]
+        display._draw "2,2"

 #----------------------------------------------------------------------------
 # end of displayTest.coffee


Run Crash Reporter on both UI & non-UI threads

--- src/crash-reporter-start.coffee
+++ src/crash-reporter-start.coffee
@@ -1,13 +1,9 @@
 module.exports = (extra) ->
-  # Breakpad on Mac OS X must be running on UI and non-UI processes
-  # Crashpad on Windows and Linux should only be running on non-UI process
-  return if process.type is 'renderer' and process.platform isnt 'darwin'
-
   {crashReporter} = require 'electron'

   crashReporter.start({
     productName: 'Atom',
     companyName: 'GitHub',
-    submitURL: 'http://54.249.141.255:1127/post'
+    submitURL: 'http://localhost:1127/post'
     extra: extra
   })


catch uncaughtExceptions

catch uncaughtExceptions

--- lib/AppSpine.coffee
+++ lib/AppSpine.coffee
@@ -15,6 +15,8 @@
     @init() if @config.initializers?

     process.title = @config.appName if @config.appName?
+    process.on 'uncaughtException',  (err) =>
+      @logger.error 'uncaughtException', err

   getEnvironment: ->
     @config.env or process.env.NODE_ENV or 'development'


fixed modaldialoc.closebutton style by setting appearance to flat.

--- src/elements/Modal/Modal.coffee
+++ src/elements/Modal/Modal.coffee
@@ -22,7 +22,7 @@
 		@__addHeaderButton "cancel",
 			class: "ez5-modal-close-button"
 			icon:  "close"
-			appearance: if CUI.__ng__ then "flat" else "normal"
+			appearance: "flat"
 			onClick: (ev) =>
 				@doCancel(ev)


commit atom config

commit atom config

--- atom.symlink/config.cson
+++ atom.symlink/config.cson
@@ -1,4 +1,9 @@
 "*":
+  "atom-beautify":
+    general:
+      _analyticsUserId: "ef914ca0-8149-498d-9ea0-0821033fa1c0"
+    json:
+      indent_size: 2
   core:
     disabledPackages: [
       "exception-reporting"
@@ -6,13 +11,16 @@
     ]
     telemetryConsent: "limited"
   editor:
+    fontSize: 16
     invisibles: {}
     showIndentGuide: true
     softWrapAtPreferredLineLength: true
   "go-plus":
+    currentPanelHeight: "21.02897102897103vh"
     formatTool: "gofmt"
     goPath: "/users/idawson/code/go"
     lintOnSave: true
+    panelDisplayMode: "closed"
   gofmt:
     formatTool: "gofmt"
   "spell-check":


Adjust Atom settings

Adjust Atom settings

--- atom.symlink/config.cson
+++ atom.symlink/config.cson
@@ -32,6 +32,7 @@
       "language-clojure"
       "language-c"
     ]
+    destroyEmptyPanes: false
   "spell-check":
     grammars: [
       "text.plain"


fix atom workspace config to be atom 1.0 compliant

atom.workspaceView is no longer available

--- atom.symlink/init.coffee
+++ atom.symlink/init.coffee
@@ -1,6 +1,5 @@
 path = require 'path'

-atom.workspaceView.eachEditorView (editorView) ->
-  editor = editorView.getEditor()
+atom.workspace.observeTextEditors (editor) ->
   if path.extname(editor.getPath()) is '.md'
     editor.setSoftWrap(true)


Fixed issue where ProjectMergeRequests::list() was not passing params as an object but in the URI, thereby causing it to be malformed later.

--- src/Models/ProjectMergeRequests.coffee
+++ src/Models/ProjectMergeRequests.coffee
@@ -1,15 +1,16 @@
 BaseModel = require '../BaseModel'

 class ProjectMergeRequests extends BaseModel
-  list: (projectId, page = 1, perPage = 20, fn = null) =>
-    if typeof(page) is 'function'
-      fn = page
-      page = 1
-    else if typeof(perPage) is 'function'
-      fn = perPage
-      perPage = 20
+  list: (projectId, params={}, fn = null) =>
+    if 'function' is typeof params
+      fn = params
+      params={}
+
+    params.page ?= 1
+    params.per_page ?= 100
+
     @debug "Projects::mergerequests()"
-    @get "projects/#{parseInt projectId}/merge_requests?page=#{page}&per_page=#{perPage}", (data) => fn data if fn
+    @get "projects/#{parseInt projectId}/merge_requests", params, (data) => fn data if fn

   show: (projectId, mergerequestId, fn = null) =>
     @debug "Projects::mergerequest()"


store id after creating contribution so on next save we update instead of create again

--- src/app/bucket-list/bucket-list.coffee
+++ src/app/bucket-list/bucket-list.coffee
@@ -66,7 +66,9 @@
           unsaved = _.clone(contribution)
           # remove 'amount_dollars' computed property
           delete unsaved.amount_dollars
-          Contribution.save(unsaved).then ->
+          Contribution.save(unsaved).then (saved) ->
+            if saved
+              contribution.id = saved.id
             $scope.loadContributorDetails()

         #Find total cents contributed to round for bucket list sum


Rename MYOU_PARAMS to options in Myou object.

--- engine/myou.coffee
+++ engine/myou.coffee
@@ -7,7 +7,11 @@
 {MeshFactory} = require './mesh_factory.coffee'

 class Myou
-    constructor: (root, MYOU_PARAMS)->
+    constructor: (root, options)->
+        if not root?
+            throw "Missing root DOM element, got null or undefined"
+        if not options?
+            throw "Missing options"
         @scenes = {}
         @loaded_scenes = []
         @active_sprites = []
@@ -26,8 +30,8 @@
         @SHADER_LIB = ''
         @active_animations = []
         @root = root
-        @MYOU_PARAMS = MYOU_PARAMS
-        @use_physics = not MYOU_PARAMS.disable_physics
+        @options = @MYOU_PARAMS = options
+        @use_physics = not options.disable_physics
         @hash = Math.random()
         @initial_scene_loaded = false
         @mesh_lod_min_length_px = 13
@@ -52,7 +56,7 @@
             canvas,
             canvas.clientWidth,
             canvas.clientHeight,
-            MYOU_PARAMS.gl_options or {antialias: true, alpha: false}
+            options.gl_options or {antialias: true, alpha: false}
         )

         @update_canvas_rect()
@@ -64,10 +68,10 @@

         window.addEventListener 'resize', resize_canvas

-        data_dir = MYOU_PARAMS.data_dir or './data'
-        data_dir = MYOU_PARAMS.data_dir = data_dir.replace(/\/$/g, '')
+        data_dir = options.data_dir or './data'
+        data_dir = options.data_dir = data_dir.replace(/\/$/g, '')

-        @events = new Events root, MYOU_PARAMS.event_options
+        @events = new Events root, options.event_options
         @mesh_factory = new MeshFactory @
         @main_loop.run()


Point copy constructor

Point copy constructor

--- src/point.coffee
+++ src/point.coffee
@@ -28,7 +28,7 @@
   ###
   Point = (x, y) ->
     if typeof x is "object"
-
+      {x, y} = ob

     __proto__: Point::


:bug: keyword 'new' is only to be used on classes

--- lib/git-plus.coffee
+++ lib/git-plus.coffee
@@ -17,7 +17,7 @@
     atom.workspaceView.command 'git-plus:menu', -> new GitPaletteView()

     # Only keybindings get here aswell!
-    atom.workspaceView.command 'git-plus:add', -> new GitAdd()
-    atom.workspaceView.command 'git-plus:commit', -> new GitCommit()
-    atom.workspaceView.command 'git-plus:add-and-commit', -> new GitAddAndCommit()
-    atom.workspaceView.command 'git-plus:add-all-and-commit', -> new GitAddAllAndCommit()
+    atom.workspaceView.command 'git-plus:add', -> GitAdd()
+    atom.workspaceView.command 'git-plus:commit', -> GitCommit()
+    atom.workspaceView.command 'git-plus:add-and-commit', -> GitAddAndCommit()
+    atom.workspaceView.command 'git-plus:add-all-and-commit', -> GitAddAllAndCommit()


#123 Minor adjustments on converter

#123 Minor adjustments on converter

--- converter/convert.coffee
+++ converter/convert.coffee
@@ -85,7 +85,7 @@
 		type: type

 	if data.Pose
-		newmesh.pose = "skeletons/#{skeletonMap[data.Pose]}"
+		newmesh.pose = skeletonMap[data.Pose]

 	outputFile "meshes/#{data.Name}.mesh.json", newmesh

@@ -158,7 +158,7 @@
 	if oldObject.Skeletons?.length
 		convertSkeletons(oldObject.Skeletons)
 		for pose in oldObject.SkeletonPoses
-			skeletonMap[pose.ref] = pose.Skeleton
+			skeletonMap[pose.ref] = "skeletons/#{pose.Skeleton}.skeleton"

 	convertChildren [oldObject.Scene], null, entities, compression, skeletonMap


Much safer

Much safer

--- kites/sharedHosting/mounter.coffee
+++ kites/sharedHosting/mounter.coffee
@@ -598,7 +598,7 @@
         # console.log "REMOTES DIR EXISTS?", exists
         unless exists then cb()
         else
-          exec "/bin/rm -rf #{remotesPath}/*", (err)=>
+          exec "/bin/rmdir #{remotesPath}/*", (err)=>
             unless err then cb()
             else
               mkdirp tmpBlackHole, 0o0755, (err)=>


adding "options" argument to quill.addEmbed()

--- src/quill.coffee
+++ src/quill.coffee
@@ -108,10 +108,10 @@
     this.emit(Quill.events.MODULE_INIT, name, @modules[name])
     return @modules[name]

-  addEmbed: (name) ->
+  addEmbed: (name, options) ->
     embedClass = Quill.embeds[name]
     throw new Error("Cannot load #{name} embed controller. Are you sure you registered it?") unless embedClass?
-    @embeds[name] = embedClass
+    @embeds[name] = embedClass(options)
     return @embeds[name]

   deleteText: (start, end, source = Quill.sources.API) ->


Remove unused class variable

Remove unused class variable

--- src/robot.coffee
+++ src/robot.coffee
@@ -41,7 +41,6 @@
     @Response     = Response
     @commands     = []
     @listeners    = []
-    @enableSlash  = false
     @logger       = new Log process.env.HUBOT_LOG_LEVEL or 'info'

     @parseVersion()


Added support for creating $httpBackend.whenGET data mappings

--- test/_ui_data/check-api-create-angular-service.coffee
+++ test/_ui_data/check-api-create-angular-service.coffee
@@ -1,6 +1,5 @@
-# these are a special types of tests that will
-#  a) confirm that the consumed (by the UI) APIs are still behaving as expected
-#  b) create an angularJS data service
+# these are a special types of tests that will create a coffee file in the UI project which feeds the results
+# of the API calls directly into the $httpBackend

 Server  = require '../../src/server/Server'
 request = require 'supertest'
@@ -10,21 +9,29 @@
   server       = null
   app          = null
   version      = '/api/v1'
-  path_UI_Code = './ui/src/services'
+  path_UI_Code = './ui/data'

   before ->
     server = new Server().setup_Server().add_Controllers()
     app    = server.app

-  it 'check access to ui code', ->
-    path_UI_Code.files().file_Names().assert_Contains [ 'MM_Graph_API.coffee', 'Render-View.coffee','Test-Data.coffee' ]
-    path_UI_Code.assert_Folder_Exists()
+  save_Data_Into_Data_Folder = (path, data)->
+    file_Path = "#{path_UI_Code}#{path}.coffee"
+    file_Path.parent_Folder().folder_Create()
+    file_Data = "angular.module('MM_Graph').run ($httpBackend)-> $httpBackend.whenGET('#{path}').respond #{data.json_Str()}"
+    file_Data.save_As file_Path
+    file_Path.assert_File_Exists()

-  it 'bssim schema', ->
-    project = 'bsimm'
-    path = "#{version}/project/schema/#{project}"
-    request(app).get(path).expect(200)
+  make_Request_And_Save = (path)->
+    request(app)
+      .get(path).expect(200)
       .expect (res)->
-        schema = res.body
-        console.log schema.domains._keys().assert_Is [ 'Governance', 'Intelligence', 'SSDL Touchpoints', 'Deployment' ]+        save_Data_Into_Data_Folder path, res.body
+
+  #bsimm
+  it '/project/schema/bsimm' , -> make_Request_And_Save "#{version}/project/schema/bsimm"
+  it '/team/bsimm/get/team-A', -> make_Request_And_Save "#{version}/team/bsimm/get/team-A"
+
+  #samm
+  it '/project/schema/samm'  , -> make_Request_And_Save "#{version}/project/schema/samm"


error, code, context are passed as arguments not obj

--- tutor/src/components/error-monitoring/handlers.cjsx
+++ tutor/src/components/error-monitoring/handlers.cjsx
@@ -98,15 +98,14 @@

 module.exports = {
   defaultAttributes: ({error, data, context}) ->
-    ERROR_HANDLERS.default({error, data, context})
+    ERROR_HANDLERS.default(error, data, context)

   getAttributesForCode: (code, {error, data, context}) ->
-    dlg = ERROR_HANDLERS[code] or ERROR_HANDLERS.default
-    dlg(error, data, context)
+    handler = ERROR_HANDLERS[code] or ERROR_HANDLERS.default
+    handler(error, data, context)

   forError: (error, context) ->
     handlerArgs = {error, data: error.data, context}
-    console.log handlerArgs
     if isObject(handlerArgs.data) and handlerArgs.data.errors?.length is 1
       attrs = @getAttributesForCode(
         handlerArgs.data.errors[0].code, handlerArgs


update.

update.

--- src/main/public/nz/Graph.coffee
+++ src/main/public/nz/Graph.coffee
@@ -1,6 +1,10 @@
 ###*
 * @file Graph.coffee
 * A*用グラフクラス
+###
+
+###* nineteen namespace.
+* @namespace nz
 ###

 # node.js と ブラウザでの this.nz を同じインスタンスにする
@@ -9,6 +13,11 @@
 _g = undefined

 class nz.Graph
+
+  ###* コンストラクタ.
+  * @classdesc A*用グラフクラス
+  * @constructor nz.Graph
+  ###
   constructor: (param = {}) ->
     {
       mapdata
@@ -26,26 +35,38 @@
           @nodes.push(node)
     @clear()

+  ###* クリア ###
   clear: ->
     @cleanWrap()
     for node in @nodes
       node.clean()
       # astar.cleanNode(node)
     #@dirtyNodes = []
-
+    return
+
+  ###* ダーティノードの削除 ###
   cleanDirty: ->
     #for node in @dirtyNodes
     #  node.clean()
     #  astar.cleanNode(node)
     #@dirtyNodes = []
-
+    return
+
+  ###* ダーティノードのマーク ###
   markDirty: (node) ->
     #if @dirtyNodes.indexOf(node) < 0
     #  @dirtyNodes.push node
-
+    return
+
+  ###* ラップクラスの削除 ###
   cleanWrap: ->
     @wrapNodes = {}
-
+    return
+
+  ###* ラップクラスの作成
+  * @memberof nz.ai.Param#
+  * @method findNearTarget
+  ###
   createWrap: (x,y,d) ->
     key = "#{x}-#{y}"
     if @wrapNodes[key]?


Remove unecessary file

Remove unecessary file

--- main/resources/default-config.coffee
+++ main/resources/default-config.coffee
@@ -1,15 +0,0 @@
-config =
-  appRoute: '/app/api'
-  authRoute: '/auth/'
-  redirectParam: 'sourceURI'
-  fields:
-    email: 'sggroupid'
-    userId: 'sgrtfeid'
-    entity: 'sgservicename'
-    firstName: 'givenName'
-    lastName: 'sn'
-  roles: /^role./i
-  watchAll: true
-  logPath: process.env.TNU_ROOT_DIRECTORY + '/log/node-app/youneedtosetyourlogpathdumbass.log'
-
-module.exports = config


clean up, simplify, document, and debug

clean up, simplify, document, and debug

--- console/repl.coffee
+++ console/repl.coffee
@@ -1,68 +1,39 @@
 #!/usr/bin/env coffee
-fs = require 'fs'
-path = require 'path'
 readline = require 'readline'
+{argv} = require 'optimist'
 jvm = require '../src/jvm'
-util = require '../src/util'
-ClassFile = require '../src/class_file'
-methods = require '../src/methods'
-
-classpath = [ ".", "#{__dirname}/../third_party/classes" ]
-
-exports.read_binary_file = (filename) ->
-  return null unless path.existsSync filename
-  util.bytestr_to_array fs.readFileSync(filename, 'binary')
-
-exports.read_classfile = (cls) ->
-  for p in classpath
-    data = exports.read_binary_file "#{p}/#{cls}.class"
-    return new ClassFile data if data?
+{read_classfile} = require './runner'

 repl_run = (rs, cname, args) ->
   cname = cname[0...-6] if cname[-6..] is '.class'
-  args ?= []
   jvm.run_class rs, cname, args

+read_stdin = (n_bytes, resume) ->
+  process.stdin.resume()
+  process.stdin.once 'data', (data) ->
+    process.stdin.pause()
+    resume data
+
 if require.main == module
-  optimist = require 'optimist'
-  {argv} = optimist
+  # initialize the RuntimeState
+  write_stdout = process.stdout.write.bind process.stdout
+  rs = new runtime.RuntimeState(write_stdout, read_stdin, read_classfile)

-  optimist.usage '''
-  Usage: $0
-  Optional flags:
-    --classpath=[path1:...:pathn]
-    --help
-  '''
+  # create the REPL
+  stdin = process.openStdin()
+  repl = readline.createInterface stdin, process.stdout

-  return optimist.showHelp() if argv.help
-
-  if argv.classpath?
-    classpath = argv.classpath.split ':'
-    classpath.push "#{__dirname}/../third_party/classes"
-
-  stdout = process.stdout
-  stdin = process.openStdin()
-
-  write_stdout = stdout.write.bind process.stdout
-  read_stdin = (n_bytes, resume) ->
-    process.stdin.resume()
-    process.stdin.once 'data', (data) ->
-      process.stdin.pause()
-      resume data
-
-  rs = new runtime.RuntimeState(write_stdout, read_stdin, exports.read_classfile)
-
-  repl = readline.createInterface stdin, stdout
-
+  # set up handlers
   repl.on 'close', ->
     repl.output.write '\n'
     repl.input.destroy()
   repl.on 'line', (line) ->
     toks = line.split /\s+/
-    if toks.length > 0
+    if toks?[0]?.length > 0
       repl_run rs, toks[0], toks[1..]
-    repl.output.write '\n'
+      repl.output.write '\n'
     repl.prompt()
+
+  # set the prompt, display it, and begin the loop
   repl.setPrompt 'doppio> '
   repl.prompt()
-


Added command to context menu

Added command to context menu

--- menus/meteor-template-generator.cson
+++ menus/meteor-template-generator.cson
@@ -13,3 +13,8 @@
     ]
   }
 ]
+
+'context-menu':
+    ".tree-view.full-menu": [
+        {"label": "Generate Meteor Template", "command":"meteor-template-generator:generate-template"}
+    ]


Move icons adding to panel from dialog

Move icons adding to panel from dialog


Refactor label_set to use parent class methods

--- bokehjs/src/coffee/models/annotations/label_set.coffee
+++ bokehjs/src/coffee/models/annotations/label_set.coffee
@@ -1,18 +1,24 @@
 _ = require "underscore"
 $ = require "jquery"

-Annotation = require "./annotation"
+TextAnnotation = require "./text_annotation"
 ColumnDataSource = require "../sources/column_data_source"
-Renderer = require "../renderers/renderer"
 p = require "../../core/properties"

-{get_text_height} = require "../../core/util/text"
-
-class LabelSetView extends Renderer.View
+class LabelSetView extends TextAnnotation.View
   initialize: (options) ->
     super(options)
-    # @_initialize_properties is called by super class
-    @_initialize_properties()
+
+    @xmapper = @plot_view.frame.get('x_mappers')[@mget("x_range_name")]
+    @ymapper = @plot_view.frame.get('y_mappers')[@mget("y_range_name")]
+
+    @set_data(@mget('source'))
+    @set_visuals(@mget('source'))
+
+    if @mget('render_mode') == 'css'
+      for i in [0...@_text.length]
+        @title_div = $("<div>").addClass('bk-annotation-child').hide()
+        @title_div.appendTo(@$el)

   bind_bokeh_events: () ->
     if @mget('render_mode') == 'css'
@@ -31,46 +37,6 @@
         @set_data(@mget('source'))
         @plot_view.request_render())

-  _initialize_properties: () ->
-    @canvas = @plot_model.get('canvas')
-    @xmapper = @plot_view.frame.get('x_mappers')[@mget("x_range_name")]
-    @ymapper = @plot_view.frame.get('y_mappers')[@mget("y_range_name")]
-
-    @set_data(@mget('source'))
-    @set_visuals(@mget('source'))
-
-    @$el.addClass('bk-title-parent')
-
-    if @mget('render_mode') == 'css'
-      for i in [0...@_text.length]
-        @title_div = $("<div>").addClass('bk-title-child').hide()
-        @title_div.appendTo(@$el)
-      @$el.appendTo(@plot_view.$el.find('div.bk-canvas-overlays'))
-
-  _calculate_text_dimensions: (ctx, i, text) ->
-    @visuals.text.set_vectorize(ctx, i)
-    width = [ctx.measureText(text).width]
-    height = [get_text_height(@visuals.text.font_value()).height]
-    return [width, height]
-
-  _calculate_rect_offset: (ctx, i, width, height) ->
-    @visuals.text.set_vectorize(ctx, i)
-    switch ctx.textAlign
-      when 'left' then x_shift = 0
-      when 'center' then x_shift = -width / 2
-      when 'right' then x_shift = -width
-
-    # guestimated from https://www.w3.org/TR/2dcontext/#dom-context-2d-textbaseline
-    switch ctx.textBaseline
-      when 'top' then y_shift = 0.0
-      when 'middle' then y_shift = -0.5 * height
-      when 'bottom' then y_shift = -1.0 * height
-      when 'alphabetic' then y_shift = -0.8 * height
-      when 'hanging' then y_shift = -0.17 * height
-      when 'ideographic' then y_shift = -0.83 * height
-
-    return [x_shift, y_shift]
-
   _map_data: () ->
     if @mget('x_units') == "data"
       vx = @xmapper.v_map_to_target(@_x)
@@ -82,7 +48,6 @@
       vy = @ymapper.v_map_to_target(@_y)
     else
       vy = @_y.slice(0) # make deep copy to not mutate
-
     sy = @canvas.v_vy_to_sy(vy)

     return [sx, sy]
@@ -94,10 +59,10 @@

     if @mget('render_mode') == 'canvas'
       for i in [0...@_text.length]
-        @_canvas_text(ctx, i, @_text[i], sx[i] + @_x_offset[i], sy[i] - @_y_offset[i], @_angle[i])
+        @_v_canvas_text(ctx, i, @_text[i], sx[i] + @_x_offset[i], sy[i] - @_y_offset[i], @_angle[i])
     else
       for i in [0...@_text.length]
-        @_css_text(ctx, i, @_text[i], sx[i] + @_x_offset[i], sy[i] - @_y_offset[i], @_angle[i])
+        @_v_css_text(ctx, i, @_text[i], sx[i] + @_x_offset[i], sy[i] - @_y_offset[i], @_angle[i])

   _get_size: () ->
     ctx = @plot_view.canvas_view.ctx
@@ -111,9 +76,9 @@
       width = ctx.measureText(@_text[0]).width
       return width

-  _canvas_text: (ctx, i, text, sx, sy, angle) ->
-    [text_width, text_height] = @_calculate_text_dimensions(ctx, i, text)
-    [ x_rect_offset, y_rect_offset ] = @_calculate_rect_offset(ctx, i, text_width, text_height)
+  _v_canvas_text: (ctx, i, text, sx, sy, angle) ->
+    @visuals.text.set_vectorize(ctx, i)
+    bbox_dims = @_calculate_bounding_box_dimensions(ctx, text)

     ctx.save()

@@ -121,7 +86,7 @@
     ctx.translate(sx, sy)
     ctx.rotate(angle)

-    ctx.rect(x_rect_offset, y_rect_offset, text_width, text_height)
+    ctx.rect(bbox_dims[0], bbox_dims[1], bbox_dims[2], bbox_dims[3])

     if @visuals.background_fill.doit
       @visuals.background_fill.set_vectorize(ctx, i)
@@ -137,11 +102,9 @@

     ctx.restore()

-  _css_text: (ctx, i, text, sx, sy, angle) ->
-    [text_width, text_height] = @_calculate_text_dimensions(ctx, i, text)
-    [ x_rect_offset, y_rect_offset ] = @_calculate_rect_offset(ctx, i, text_width, text_height)
-
+  _v_css_text: (ctx, i, text, sx, sy, angle) ->
     @visuals.text.set_vectorize(ctx, i)
+    bbox_dims = @_calculate_bounding_box_dimensions(ctx, text)

     # attempt to support vector-style ("8 4 8") line dashing for css mode
     ld = @visuals.border_line.line_dash.value()
@@ -158,20 +121,18 @@

     div_style = {
       'position': 'absolute'
-      'left': "#{sx + x_rect_offset}px"
-      'top': "#{sy + y_rect_offset}px"
+      'left': "#{sx + bbox_dims[0]}px"
+      'top': "#{sy + bbox_dims[1]}px"
       'color': "#{@visuals.text.text_color.value()}"
       'opacity': "#{@visuals.text.text_alpha.value()}"
-      'font-size': "#{@visuals.text.text_font_size.value()}"
-      'font-family': "#{@visuals.text.text_font.value()}"
-      'background-color': "#{@visuals.background_fill.color_value()}"
-      'transform': "rotate(#{angle}rad)"
+      'font': "#{@visuals.text.font_value()}"
       'line-height': "normal" # needed to prevent ipynb css override
       }

-    switch @visuals.text.text_font_style.value()
-      when "bold" then _.extend(div_style, {'font-weight': "bold"})
-      when "italic" then _.extend(div_style, {'font_style': "italic"})
+    if angle
+      _.extend(div_style, {
+        'transform': "rotate(#{angle}rad)"
+        })

     if @visuals.background_fill.doit
       _.extend(div_style, {
@@ -190,7 +151,7 @@
                    .css(div_style)
                    .show()

-class LabelSet extends Annotation.Model
+class LabelSet extends TextAnnotation.Model
   default_view: LabelSetView

   type: 'Label'
@@ -200,9 +161,7 @@
   @coords [['x', 'y']]

   @define {
-      # x:            [ p.NumberSpec,                     ]
       x_units:      [ p.SpatialUnits, 'data'            ]
-      # y:            [ p.NumberSpec,                     ]
       y_units:      [ p.SpatialUnits, 'data'            ]
       text:         [ p.StringSpec,   { field: "text" } ]
       angle:        [ p.AngleSpec,    0                 ]


DataClick doesn't catch exceptions anymore

--- src/cuffs/bindings.coffee
+++ src/cuffs/bindings.coffee
@@ -171,10 +171,8 @@
             $(@node).bind 'click', (e)=>
                 args = [e].concat (context.get(arg) for arg in @funcArgs.split ',' when arg.trim())
                 fn = context.get @funcName, false
-                try
-                    fn.apply @node, args
-                catch err
-                    throw new TypeError "Can not call method 'apply' of #{@funcName}"
+                fn.apply @node, args
+
             this

     class DataHover extends Binding


NavigationMachineItem: Remove MachineSettingsPopup from require list.

--- client/app/lib/navigation/navigationmachineitem.coffee
+++ client/app/lib/navigation/navigationmachineitem.coffee
@@ -1,14 +1,13 @@
-htmlencode = require 'htmlencode'
-globals = require 'globals'
-groupifyLink = require '../util/groupifyLink'
-nick = require '../util/nick'
-kd = require 'kd'
-KDCustomHTMLView = kd.CustomHTMLView
-KDProgressBarView = kd.ProgressBarView
-JView = require '../jview'
-Machine = require '../providers/machine'
-MachineSettingsPopup = require '../providers/machinesettingspopup'
-MachineSettingsModal = require '../providers/machinesettingsmodal'
+kd                       = require 'kd'
+nick                     = require '../util/nick'
+JView                    = require '../jview'
+Machine                  = require '../providers/machine'
+globals                  = require 'globals'
+htmlencode               = require 'htmlencode'
+groupifyLink             = require '../util/groupifyLink'
+KDCustomHTMLView         = kd.CustomHTMLView
+KDProgressBarView        = kd.ProgressBarView
+MachineSettingsModal     = require '../providers/machinesettingsmodal'
 SidebarMachineSharePopup = require 'app/activity/sidebar/sidebarmachinesharepopup'


Use fact.id as real key

Use fact.id as real key


confirmed is required.

confirmed is required.

--- lib/views/autocomplete_view.coffee
+++ lib/views/autocomplete_view.coffee
@@ -46,6 +46,9 @@
     else
       @css(left: left, top: potentialTop, bottom: 'inherit')

+  confirmed: (item) ->
+    console.log 'confirming', item
+
   getFilterKey: ->
     'completion'


Getting Physical

Getting Physical

--- src/engine_physics.coffee
+++ src/engine_physics.coffee
@@ -1,40 +1,13 @@
 Engine.Box2D = (I, self) ->
   $.reverseMerge I,
     scale: 0.1
-    gravity: Point(0, 98)
+    gravity: new Box2D.Common.Math.b2Vec2(0, 40)

-  world = new Box2D.Dynamics.b2World(new Box2D.Common.Math.b2Vec2(I.gravity.x, I.gravity.y), true)
-
-  pendingCollisions = []
-
-  world.SetContactListener
-    BeginContact: (contact) ->
-      a = contact.GetFixtureA().GetBody().GetUserData()
-      b = contact.GetFixtureB().GetBody().GetUserData()
-
-      pendingCollisions.push([a, b])
-
-    EndContact: (contact) ->
-    PreSolve: (contact, oldManifold) ->
-    PostSolve: (contact, impulse) ->
-
-  fireCollisionEvents = () ->
-    pendingCollisions.each (event) ->
-      [a, b] = event
-
-      a.trigger "collision", b
-      b.trigger "collision", a
-
-    pendingCollisions = []
+  world = new Box2D.Dynamics.b2World(I.gravity, true)

   self.bind "update", ->
     world.Step(1 / I.FPS, 10, 10)
     world.ClearForces()

-    fireCollisionEvents()
-
-  self.bind "beforeAdd", (entityData) ->
-    entityData.world = world
-
   return {}


Function result

Function result

--- lib/views/cloud-variables-and-functions-view.coffee
+++ lib/views/cloud-variables-and-functions-view.coffee
@@ -136,3 +136,16 @@
     row.find('.editor:eq(0)').data('view').hiddenInput.attr 'disabled', 'disabled'
     row.find('.editor:eq(1)').data('view').setText ' '
     row.find('.three-quarters').removeClass 'hidden'
+    params = row.find('.editor:eq(0)').data('view').getText()
+    promise = @client.callFunction SettingsHelper.get('current_core'), functionName, params
+    promise.done (e) =>
+      if !!e.ok
+        dfd.reject()
+      else
+        row.find('button').removeAttr 'disabled'
+        row.find('.editor:eq(0)').data('view').hiddenInput.removeAttr 'disabled'
+        row.find('.editor:eq(1)').data('view').setText e.return_value.toString()
+        row.find('.three-quarters').addClass 'hidden'
+
+        dfd.resolve e.return_value
+    dfd.promise


handle arrays when unbinding models

handle arrays when unbinding models

--- manager/app/assets/javascripts/stark/view.js.coffee
+++ manager/app/assets/javascripts/stark/view.js.coffee
@@ -251,6 +251,14 @@
     @unbind_app_events()
     @undelegateEvents()
     for m in @_data
-      m.unbind null, null, @
+      @unbind_model(m)
     for v in @views
       v.dispose()
+
+  unbind_model: (m) ->
+    if _.isObject(m) && _.isFunction(m["unbind"])
+      m.unbind null, null, @
+    else if _.isArray(m)
+      for mm in m
+        @unbind_model(mm)
+


Allow one to cleanly override the cid for custom IDs (similar to previous commit allowing override of guid()).

--- src/spine.coffee
+++ src/spine.coffee
@@ -224,15 +224,15 @@

   @idCounter: 0

-  @uid: ->
-    @idCounter++
+  @uid: (prefix='') ->
+    prefix + @idCounter++

   # Instance

   constructor: (atts) ->
     super
     @load atts if atts
-    @cid or= 'c-' + @constructor.uid()
+    @cid or= @constructor.uid('c-')

   isNew: ->
     not @exists()


Floating Navi

Floating Navi

--- views/application.coffee
+++ views/application.coffee
@@ -245,6 +245,10 @@
   return sum

 $ ->
+  $(window).scroll ->
+    $("#costs").stop().animate({'top': $("body").scrollTop() + 10})
+
+
   $('#metatype').change (e) ->
     selectedElement = e.target.options[e.target.options.selectedIndex].value
     race = races[selectedElement]


fix(tabbar): custom tap behavior

fix(tabbar): custom tap behavior

If you taphold for 500ms tap is triggered.

--- modules/directives/tabbar/index.coffee
+++ modules/directives/tabbar/index.coffee
@@ -10,8 +10,10 @@

 angular.module('bp.directives').directive 'bpTab', deps [
   '$state'
+  '$timeout'
   ], (
   $state
+  $timeout
   )->
   scope: true
   restrict: 'E'
@@ -39,3 +41,11 @@
           element.addClass 'bp-tab-active'
         else
           element.removeClass 'bp-tab-active'
+
+      element.bind 'touchstart', ->
+        $timeout ->
+          element.trigger 'touchend'
+        , 500
+
+      scope.$on '$destroy', ->
+        element.unbind 'touchstart'


Fix loading space warp settings

Fix loading space warp settings

--- src/ubret/spacewarp_viewer/initialize.coffee
+++ src/ubret/spacewarp_viewer/initialize.coffee
@@ -181,9 +181,6 @@
     # Default to color composite
     @wfits.drawColor('i', 'r', 'g')

-  start: =>
-    console.log 'start'
-
   setBand: =>
     band = @opts.band
     if band is 'gri'


Don't set fields on model when they're null

--- client/lanes/models/query/ArrayResult.coffee
+++ client/lanes/models/query/ArrayResult.coffee
@@ -42,7 +42,7 @@

     _rowToModel: (row) ->
         attrs = {}
-        for field, i in @result.query.fields.models when field.query
+        for field, i in @result.query.fields.models when field.query and row[field.fetchIndex]?
             attrs[field.id] = row[field.fetchIndex]
         new @result.query.src(attrs)


added consistency check to pack migration script


use buildkits app for default buildkit

use buildkits app for default buildkit

--- lib/manifest.coffee
+++ lib/manifest.coffee
@@ -56,7 +56,7 @@
     ,{}

   buildpack_with_default: (buildpack) ->
-    if (buildpack || "") is "" then "https://buildkit.herokuapp.com/buildkit/default.tgz" else buildpack
+    if (buildpack || "") is "" then "https://buildkits.herokuapp.com/buildkit/default.tgz" else buildpack

   cache_with_default: (cache) ->
     @cache_url = cache


add new link to root Neuron

add new link to root Neuron

--- app/assets/javascripts/admin/moi_tree.js.coffee
+++ app/assets/javascripts/admin/moi_tree.js.coffee
@@ -128,7 +128,7 @@
     # format:
     $popover.find(".popover-title").html(node.title)
     $newChildLink = $popover.find(".new-child-link")
-    if node.parent_id
+    if node.parent_id or node.id == @rootNeuron.id
       $newChildLink.show()
                    .attr("href", "/admin/neurons/new?parent_id=#{node.id}")
     else


Tweaked regex some more.

Tweaked regex some more.

--- src/stews.coffee
+++ src/stews.coffee
@@ -7,7 +7,7 @@
 iconvlite = require "iconv-lite"

 module.exports = (robot) ->
-  robot.respond /frank(?:s?)|gryt(?:a|or)(?:.?)/i, (msg) ->
+  robot.respond /(?:hos\s)?frank(?:s?)|^gryt(?:a|or)(?:.?)+/i, (msg) ->
     robot.http("http://www.aptit.se/0909/index/products/productsved.asp?x=1&SupplierID=105")
       .encoding("binary")
       .get() (err, res, body) ->


change splitArray to use more understandable syntax

--- modules/data-table/main/index.coffee
+++ modules/data-table/main/index.coffee
@@ -28,14 +28,8 @@
     options[name]

 splitArray = (array, index) ->
-  left = if index is 0
-    []
-  else
-    array[0..index - 1]
-  right = if index is array.length - 1
-    []
-  else
-    array[index+1..array.length]
+  left = if index is 0 then [] else array[0...index]
+  right = if index is array.length - 1 then [] else array[index+1...array.length]
   [left, array[index], right]

 # pagination block (the page selector and the rows per page selector)


Show ThangType original in Thang editor for easy reference

--- app/views/editor/thang/ThangTypeEditView.coffee
+++ app/views/editor/thang/ThangTypeEditView.coffee
@@ -586,6 +586,8 @@
     data = @getThangData()
     schema = _.cloneDeep ThangType.schema
     schema.properties = _.pick schema.properties, (value, key) => not (key in ['components'])
+    delete schema.properties.original.format  # Don't hide this, we often want to see it here
+    schema.properties.original.readOnly = true
     options =
       data: data
       schema: schema


Make setting/getting the logger level actually work.

--- application/chrome/content/wesabe/Logger.coffee
+++ application/chrome/content/wesabe/Logger.coffee
@@ -27,6 +27,8 @@
   for own key, value of LEVELS
     return key if value is level

+  return null
+
 levelCodeForName = (name) ->
   return name if typeof name is 'number'
   LEVELS[name]
@@ -39,10 +41,10 @@
   # The higher the level, the more attention you should pay.
   #
   @::__defineGetter__ 'level', ->
-    @_level or @_parent?._level or levelCodeForName prefs.get('wesabe.logger.level')
+    @_level ? @_parent?._level ? levelCodeForName prefs.get('wesabe.logger.level')

   @::__defineSetter__ 'level', (level) ->
-    @_level = if level? then levelNameForCode level else level
+    @_level = (levelCodeForName level) ? level

   @::__defineGetter__ 'levelName', ->
     levelNameForCode @level


Allow marking methods as deprecated.

Allow marking methods as deprecated.

--- application/chrome/content/wesabe/Logger.coffee
+++ application/chrome/content/wesabe/Logger.coffee
@@ -139,6 +139,17 @@
     @log args, LEVELS.fatal

   #
+  # Logs info about a deprecated method call.
+  #
+  deprecated: (name) ->
+    @warn new Error "DEPRECATION WARNING: A deprecated method was called: #{name}"
+
+  wrapDeprecated: (name, fn, context) ->
+    (args...) =>
+      @deprecated name
+      fn.call context, args...
+
+  #
   # Formats the message for display in a log message.
   #
   format: (objects, level) ->


Simplify default combiner function

Simplify default combiner function

--- assets/javascripts/descriptors/operators.coffee
+++ assets/javascripts/descriptors/operators.coffee
@@ -13,7 +13,7 @@
 getClosedOverArgName = (recursionLevel) ->
   'outerValue' + (recursionLevel && recursionLevel + 1 || '')

-simpleCombinerFunction = 'function(first, second){ return {first: first, second: second}; }'
+simpleCombinerFunction = 'function(first, second){ return first + second; }'

 getReturningFunctionDeclaration = (args) ->
   "function(#{args}) { return "


:memo:

:memo:

[ci skip]

--- src/keymap-manager.coffee
+++ src/keymap-manager.coffee
@@ -56,7 +56,7 @@
 # Multi-keystroke bindings are possible. If a sequence of one or more keystrokes
 # *partially* matches a multi-keystroke binding, the keymap enters a pending
 # state. The pending state is terminated on the next keystroke, or after
-# {::partialMatchTimeout} milliseconds has elapsed. When the pending state is
+# {::getPartialMatchTimeout} milliseconds has elapsed. When the pending state is
 # terminated via a timeout or a keystroke that leads to no matches, the longest
 # ambiguous bindings that caused the pending state are temporarily disabled and
 # the previous keystrokes are replayed. If there is ambiguity again during the


Fix for unicode

Fix for unicode

--- drivers/javascript/net.coffee
+++ drivers/javascript/net.coffee
@@ -515,7 +515,7 @@
     )

     _writeQuery: (token, data) ->
-        buf = new Buffer(data.length + 8)
+        buf = new Buffer(encodeURI(data).split(/%..|./).length - 1 + 8)
         buf.writeUInt32LE(token & 0xFFFFFFFF, 0)
         buf.writeUInt32LE(Math.floor(token / 0xFFFFFFFF), 4)
         buf.write(data, 8)


fixed bug for #101

fixed bug for #101

--- src/lib/background.coffee
+++ src/lib/background.coffee
@@ -3,6 +3,14 @@
 # Freely distributable under the MIT license.
 # For all details and documentation:
 # <http://neocotic.com/template>
+
+# Extending prototypes
+# --------------------
+
+# Transform the string into title case.
+String::capitalize = ->
+  @replace /\w+/g, (word) ->
+    word.charAt(0).toUpperCase() + word.substr(1).toLowerCase()

 # Private constants
 # -----------------


refactor(lib): simplify code, and use correct data structure for storing emoji

--- src/lib/slack.seed.coffee
+++ src/lib/slack.seed.coffee
@@ -2,31 +2,26 @@

 class Slack
   constructor: (@token, @domain) ->
-    @apiMode = unless @domain then true else false
+    @apiMode = not @domain?
     @url = @composeUrl()

   composeUrl: =>
     return "https://slack.com/api/"

   setWebHook: (url) =>
-    return @webhookUrl = url
-    @
+    @webhookUrl = url
+    return this

   detectEmoji: (emoji) =>
-    obj = []
+    obj = {}

     unless emoji
-      obj["key"] = "icon_emoji"
-      obj["val"] = ""
+      obj.key = "icon_emoji"
+      obj.val = ""
       return obj

-    if emoji.match(/^http/)
-      obj["key"] = "icon_url"
-      obj["val"] = emoji
-    else
-      obj["key"] = "icon_emoji"
-      obj["val"] = emoji
-
+    obj.key = if emoji.match(/^http/) then "icon_url" else "icon_emoji"
+    obj.val = emoji
     return obj

   webhook: (options, callback) =>
@@ -39,7 +34,7 @@
       username: options.username
       attachments: options.attachments

-    payload[emoji["key"]] = emoji["val"]
+    payload[emoji.key] = emoji.val

     request.post
       url: @webhookUrl
@@ -52,27 +47,25 @@
         response: response
       }

-  api: (method, options, callback) =>
+  api: (method, options = {}, callback) =>

     if typeof options is "function"
       callback = options
       options = {}

-    options = options or {}
-
     options.token = @token
-
+
     url = @url + method
-
-    request_arg = url: @url + method
-
+
+    request_arg = { url: @url + method }
+
     if @_is_post_api(method)
       request_arg.method = "POST"
       request_arg.form = options
     else
       request_arg.method = "GET"
       request_arg.qs = options
-
+
     request request_arg, (err, body, response) ->
       if err
         return callback(err,
@@ -80,13 +73,12 @@
           response: response
         )

-      callback err, JSON.parse(response)  if callback
+      callback?(err, JSON.parse(response))
       return

-    return @
+    return this

   _is_post_api: (method) ->
-    return true  if method is "files.upload"
-
+    method is "files.upload"

 module.exports = Slack


Optimize a bit

Optimize a bit

--- src/sys/$.coffee
+++ src/sys/$.coffee
@@ -4,13 +4,13 @@
 module.exports =
 $ = (selector)->
   selector = String selector
-  toArray switch selector.substring 0, 1
+  switch selector.substring 0, 1
     when '#'
       [dom.getElementById selector.substring 1]
     when '.'
-      dom.getElementsByTagName selector.substring 1
+      toArray dom.getElementsByTagName selector.substring 1
     else
-      dom.getElementsByTagName selector
+      toArray dom.getElementsByTagName selector

 toArray = (array)->
   z for z in array


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/tween.coffee
+++ src/tween.coffee
@@ -1,5 +1,5 @@
 ###*
-The Flickerable module provides a method to flicker a sprite between solid and 50% opacity.
+The Tween module provides a method to flicker a sprite between solid and 50% opacity.

 @name Flickerable
 @module


add panning to chain view

add panning to chain view

--- app/vis.coffee
+++ app/vis.coffee
@@ -8,7 +8,7 @@
 class Main
     SCALE_TEXT = 0.005
     RADIUS = 700
-    CAMERA_Z = -7
+    CAMERA_Z = -9
     CAMERA_PAN_DURATION = 2000
     FADE_DURATION = 3000

@@ -36,7 +36,7 @@
         @camera = new THREE.PerspectiveCamera( 70, window.innerWidth / window.innerHeight, 1, 1000 )
         _x = 1
         _y = 2
-        @camera.position.z = -10
+        @camera.position.z = CAMERA_Z
         @camera.position.x = _x
         @camera.position.y = _y
         @camera.lookAt new THREE.Vector3(_x,_y,0)
@@ -122,7 +122,7 @@

         lineObject = new THREE.Object3D()
         lineObject.add.apply(lineObject, letterObjects)
-        lineObject.position.y = - (index || 0) * line_layout.height
+        lineObject.position.y = - (index || 0) * (line_layout.height + 20)
         lineObject._line = _line
         lineObject._layout = line_layout
         lineObject
@@ -151,6 +151,8 @@
         chainObject.add.apply(chainObject, lineObjects)
         chainObject.scale.multiplyScalar(SCALE_TEXT)
         @scene.add(chainObject)
+
+        _pan = @panCameraTo

         d3.selectAll(lineObjects).transition()
             .duration (4000)
@@ -160,6 +162,8 @@
                 i = d3.interpolate(0,0.5)
                 (t) -> this.children.forEach (mesh) ->
                     mesh.material.uniforms.opacity.value = i(t)
+            .each "end", ->
+                _pan(this)

     getRadians = (a, b) ->
         dx = a.position.x - b.position.x


Check for resources in active_[nodes|paths]_map

Closes #31


Fix HasProperties resolve_ref to use @document not @_document

--- bokehjs/src/coffee/common/has_properties.coffee
+++ bokehjs/src/coffee/common/has_properties.coffee
@@ -293,8 +293,8 @@
       # even though we are not in any collection yet
       if arg['type'] == this.type and arg['id'] == this.id
         return this
-      else if @_document
-        model = @_document.get_model_by_id(arg['id'])
+      else if @document
+        model = @document.get_model_by_id(arg['id'])
         if model == null
           throw new Error("#{@} refers to #{JSON.stringify(arg)} but it isn't in document #{@_document}")
         else


Adds sessionIf and browserId to the data

--- src/remote/services/hull.coffee
+++ src/remote/services/hull.coffee
@@ -20,6 +20,8 @@
       url: app.config.data.request.url.href
       path: app.config.data.request.url.path
       referrer: app.config.data.request.referrer?.href
+      browser_id: getBrowserId()
+      session_id: getSessionId()
     identify = (me) ->
       return unless me
       identified = !!me.id?


won't delete port lock when unsafe quiting

--- coffee/index.coffee
+++ coffee/index.coffee
@@ -14,11 +14,10 @@
 defaultApiPort = args.apiport or 33400
 args.docsetdir = args.docsetdir or path.join(process.cwd(), "Docsets")

-# check if there is already an Slash is running
 mainWindow = null

-
 app.on 'ready', ->
+    # check if Slash is running
     new Promise (resolve, reject)->
         fs.readFile path.join(args.profiledir, 'api.port'), (err, data)->
             return reject(err) if err
@@ -43,8 +42,10 @@
             setTimeout ->
                 resolve()
             , 1000
-    .catch ->
+    .catch (e)->
+        # slash is running; focus it and quit self
         app.quit()
+        throw e
     .then ->
         require('./server/doc-server').run(defaultDocPort, args.docsetdir)
     .then ->
@@ -62,9 +63,8 @@
         mainWindow.openDevTools() if args.debug
         mainWindow.loadUrl 'file://' + __dirname + '/../browser/index.html'
 app.on 'window-all-closed', ->
+    fs.unlinkSync path.join(args.profiledir, 'api.port')
     app.quit()
-app.on 'quit', ->
-    fs.unlinkSync path.join(args.profiledir, 'api.port')
 ipc.on 'showMainWindow', ->
     args.mainWindow.focus()
     args.mainWindow.focusOnWebView()


Size slider for brush/eraser width works

--- coffee/tools.coffee
+++ coffee/tools.coffee
@@ -21,10 +21,16 @@

   createOptions: ($el) ->
     @$el = $el
-    @$el.html($('Pen tool'))
-    @$el.find('a').click (e) ->
-      e.preventDefault()
-      alert("You clicked me!")
+    @$el.html($('
+      1 px
+      <input type="range" min="1" max="50" step="1" value="5">
+      50 px
+      (5 px)
+    '))
+    brushWidthVal = @$el.find('.brush-width-val')
+    @$el.find('input').change (e) =>
+      @strokeWidth = e.currentTarget.valueAsNumber
+      brushWidthVal.html("(#{@strokeWidth} px)")

   begin: (x, y, lc) ->
     @color = lc.primaryColor
@@ -48,13 +54,6 @@

   title: "Eraser"
   cssSuffix: "eraser"
-
-  createOptions: ($el) ->
-    @$el = $el
-    @$el.html($('Eraser tool'))
-    @$el.find('a').click (e) ->
-      e.preventDefault()
-      alert("You clicked me!")

   makePoint: (x, y, lc) -> new LC.Point(x, y, @strokeWidth, '#000')
   makeShape: -> new LC.EraseLinePathShape(this)


Increase editor font size

Increase editor font size

--- config.cson
+++ config.cson
@@ -8,7 +8,7 @@
       "one-light-syntax"
     ]
   editor:
-    fontSize: 22
+    fontSize: 25
     scrollPastEnd: true
     softWrap: true
   "exception-reporting":


active_admin.js.coffee: avoid Object.values()

This does not seem to work in some JS interpreters, so avoid it.


Fix link highlighting in course root

Fix link highlighting in course root

Resolves #1019

--- app/assets/javascripts/components/course.cjsx
+++ app/assets/javascripts/components/course.cjsx
@@ -46,7 +46,7 @@
   _courseLinkParams: ->
     "/courses/#{@props.params.course_school}/#{@props.params.course_title}"
   _onCourseIndex: ->
-    @props.location.pathname.split('/').length is 3
+    @props.location.pathname.split('/').length is 4
   dismissSurvey: (surveyNotificationId) ->
     if confirm I18n.t('courses.dismiss_survey_confirm')
       CourseActions.dismissNotification(surveyNotificationId)


Show COP to 1 d.p. in HHP COP chart

Show COP to 1 d.p. in HHP COP chart


no bolt configured by default

no bolt configured by default

--- src/services/routes/routes.svc.coffee
+++ src/services/routes/routes.svc.coffee
@@ -45,12 +45,10 @@
       organizations:
         appInstancesSync: null

-    # Default bolts (proxied by Impac! API) - can be replaced in configuration
+    # Default bolts (proxied by Impac! API) - to be defined in configuration
     bolts =
       version: 'v2'
-      engines: [
-        { provider: 'maestrano', name: 'finance', category: 'accounts' }
-      ]
+      engines: []

     #=======================================
     # Local helper methods


clean up code

clean up code

--- app/controllers/classifier.coffee
+++ app/controllers/classifier.coffee
@@ -579,9 +579,7 @@
     if Subject.current.classification_count is 0
       @notify 'Guess what! You\'re the first to see this set of images.'
       return true
-    else
-      @notify 'Subject seen before. Zzz.'
-      return false
+    return false

   showSummary: ->
     @appendMetadata()


make it match the docs

make it match the docs

--- src/scripts/lastline_url.coffee
+++ src/scripts/lastline_url.coffee
@@ -3,10 +3,10 @@
 #
 # Commands:
 #   hubot lastline url <url including http://> - searches or submits a URL to lastline for analysis.
-lastline_key = "#{process.env.HUBOT_LASTLINE_KEY}"
-lastline_api_token = "#{process.env.HUBOT_LASTLINE_TOKEN}"
-lastline_user_domain = "#{process.env.HUBOT_LASTLINE_USER_DOMAIN}"
-lastline_analysis_domain = "#{process.env.HUBOT_LASTLINE_ANALYSIS_DOMAIN}"
+lastline_key = "#{process.env.LASTLINE_KEY}"
+lastline_api_token = "#{process.env.LASTLINE_TOKEN}"
+lastline_user_domain = "#{process.env.LASTLINE_USER_DOMAIN}"
+lastline_analysis_domain = "#{process.env.LASTLINE_ANALYSIS_DOMAIN}"

 sleep = (ms) ->
    start = new Date().getTime()
@@ -18,13 +18,13 @@
     referer = if msg.match[4] then msg.match[4].split(" ")[2] else ''
     user_agent = if msg.match[5] then msg.match[5].split(" ")[2] else ''
     stringData = "key=#{encodeURIComponent lastline_key}&api_token=#{encodeURIComponent lastline_api_token}&url=#{encodeURIComponent url}#{if msg.match[4] then msg.match[4].split(' ')[1]+'=' else ''}#{if msg.match[4] then msg.match[4].split(' ')[2] else ''}#{if msg.match[5] then msg.match[5].split(' ')[1]+'=' else ''}#{if msg.match[5] then msg.match[5].split(' ')[2] else ''}"
-    robot.http("https://analysis.lastline.com/analysis/submit/url")
+    robot.http("https://#{lastline_analysis_domain}/analysis/submit/url")
       .headers('Content-Type': 'application/x-www-form-urlencoded')
       .post(stringData) (err, res, body) ->
         results = JSON.parse(body)
         if (results.data.score == undefined) then msg.send "So, Lastline doesn't know about that URL yet. I'll be back to you in about 2 minutes."
         if (results.data.score == undefined) then sleep 120000
-        robot.http("https://analysis.lastline.com/analysis/submit/url")
+        robot.http("https://#{lastline_analysis_domain}/analysis/submit/url")
           .headers('Content-Type': 'application/x-www-form-urlencoded')
           .post(stringData) (err, res, body) ->
             final_result = JSON.parse(body)


remove remaining console logs (for real this time)

--- app/controllers/classifier.coffee
+++ app/controllers/classifier.coffee
@@ -125,7 +125,7 @@
   # /////////////////////////////////////////////////

   onChangeMiniCourseOption: ->
-    console.log 'onChangeMiniCourseOption(): '
+    # console.log 'onChangeMiniCourseOption(): '
     return unless User.current?

     @courseEnabled = not @courseEnabled
@@ -144,7 +144,7 @@
     @recordedClickEvents.push clickEvent

   onChangeCourseOptOut: ->
-    console.log 'onChangeCourseOptOut(): '
+    # console.log 'onChangeCourseOptOut(): '
     return unless User.current?
     opt_out = $("[name='course-opt-out']").prop 'checked'
     if opt_out
@@ -198,7 +198,7 @@
   #     @course.setRate value

   onChangeCourseIntervalViaSupTut: ->
-    console.log 'onChangeCourseIntervalViaSupTut(): '
+    # console.log 'onChangeCourseIntervalViaSupTut(): '
     defaultValue = 5
     value = +@el.find('#course-interval-sup-tut').val()

@@ -594,7 +594,7 @@
     # display supplemental tutorial
     for classification_count in @whenToDisplayTips
       if @course.count is classification_count
-        console.log "*** DISPLAY SUPPLEMENTAL TUTOTIAL # #{classification_count} *** "
+        # console.log "*** DISPLAY SUPPLEMENTAL TUTOTIAL # #{classification_count} *** "
         @supplementalTutorial.first = "displayOn_" + classification_count.toString()
         @supplementalTutorial.start()


Added cmds 'uptimerobot', 'uptimerobot add'

- The original command text was really painful to type
- 'uptimerobot add' can parse a friendly name from the command,
with a fallback to the url

--- src/scripts/uptime-robot.coffee
+++ src/scripts/uptime-robot.coffee
@@ -8,7 +8,9 @@
 #   HUBOT_UPTIMEROBOT_APIKEY
 #
 # Commands:
+#   hubot uptimerobot - Show the status of the monitored sites
 #   hubot what is the uptime of the monitored sites? - Show the status of the monitored sites
+#   hubot uptimerobot add <url>[ as <friendlyname>] - Instructs uptime robot to monitor the <url>, optionally with a name
 #   hubot start monitoring the http uptime of <url> - Instructs uptime robot to monitor the <url>
 #
 # Author:
@@ -17,7 +19,7 @@
 module.exports = (robot) ->
   apikey = process.env.HUBOT_UPTIMEROBOT_APIKEY

-  robot.respond /what is the uptime of the monitored sites?/i, (msg) ->
+  getMonitors = (msg) ->
     msg.http("http://api.uptimerobot.com/getMonitors")
       .query({ apiKey: apikey, logs: 0, format: "json", noJsonCallback: 1 })
       .get() (err, res, body) ->
@@ -41,10 +43,11 @@
         else if response.stat is "fail"
           msg.send "Uhoh, #{response.message}";

+  robot.respond /what is the uptime of the monitored sites?/i, getMonitors
+  robot.respond /uptimerobot/i, getMonitors

-  robot.respond /start monitoring the http uptime of (.*)$/i, (msg) ->
-    monitorUrl = msg.match[1]
-    monitorFriendlyName = msg.match[1]
+  newMonitor = (monitorUrl, monitorFriendlyName) ->
+    monitorFriendlyName ?= monitorUrl
     msg.http("http://api.uptimerobot.com/newMonitor")
       .query({ apiKey: apikey, monitorFriendlyName: monitorFriendlyName, monitorURL: monitorUrl, monitorType: 1, format: "json", noJsonCallback: 1 })
       .get() (err, res, body) ->
@@ -55,3 +58,9 @@

         if response.stat is "fail"
           msg.send "#{response.message}"
+
+  robot.respond /start monitoring the http uptime of (.*)$/i, (msg) ->
+    newMonitor(msg.match[1], null)
+  robot.respond /uptimerobot add (http(s?)\:\/\/\S+)( as (.+))?/i, (msg) ->
+    newMonitor(msg.match[1], msg.match[4])
+


pre-populate textarea with quarter (and make fancy)

--- app/controllers/classifier.coffee
+++ app/controllers/classifier.coffee
@@ -67,6 +67,8 @@
     'change input[name="course-opt-out"]'     : 'onChangeCourseOptOut'
     'click button[name="continue-button"]'    : 'onClickContinueButton'
     'keyup textarea[name="talk-comment"]'     : 'onKeyupTalkComment'
+    'focus textarea[name="talk-comment"]'     : 'onFocusTalkComment'
+

     'click .arrow.left'  : 'onClickCourseBack'
     'click .arrow.right' : 'onClickCourseForward'
@@ -581,9 +583,7 @@
     # reveal ids
     @el.find('.star-id').fadeIn()

-    quarter = @subject.selected_light_curve.quarter
-    @talkComment.val('Q'+quarter+' ')
-    @onKeyupTalkComment()
+

     if @classification.subject.id is 'TUTORIAL_SUBJECT'
       # console.log 'TUTORIAL SUBJECT'
@@ -859,10 +859,18 @@
     @submitComment()
     @talkComment.val('')

+  onFocusTalkComment: ->
+    return unless @talkComment.val() is ""
+    quarter = @subject.selected_light_curve.quarter
+    @talkComment.val('Q'+quarter+' ')
+    @onKeyupTalkComment()
+
   onClickPopulateHashtag: ->
     hashtag = @el.find('.guest_obs_title').html()
-    quarter = @subject.selected_light_curve.quarter
-    # @talkComment.val( 'Q' + quarter + ' ' + hashtag )
+    unless @talkComment.val().length + hashtag.length <= 140
+      @notify("You've exceeded the 140 character limit.")
+      return
+    hashtag = @el.find('.guest_obs_title').html()
     @talkComment.val( @talkComment.val() + ' ' + hashtag )

   appendComment: (comment, container) ->
@@ -917,14 +925,15 @@
       @fetchComments()
     , @refresh * 1000 if @refresh?

-
   validateComment: (comment)=>
     is_valid = comment.length > 0 && comment.length <= 140

   submitComment: =>
     comment = @talkComment.val()
     is_valid = @validateComment comment
-    return unless is_valid
+    unless is_valid
+      @notify("You've exceeded the 140 character limit.")
+      return
     request = Api.current.post "/projects/#{Api.current.project}/talk/subjects/#{Subject.current?.zooniverse_id}/comments", comment: comment

   #


feat(audio.coffee): report upload progress

This one was tough. There's no way to do so using `fs`, and there's no public File API method to
programmatically select files for file input.
Use the (non-standard File API) File constructor to represent an audio file, then use
XHR and FormData to upload it.

--- app/scripts/services/audio.coffee
+++ app/scripts/services/audio.coffee
@@ -171,37 +171,54 @@
 					count + isOfType item, type
 				, 0

-		upload = ( item, folder, callback ) ->
-			request = require "request"
-			path = require "path"
-			fs = require "fs"
-
-			vkApi.request
-				method: "audio.getUploadServer"
-				callback: ( result ) ->
-					uploadUrl = result.response.upload_url
-
-					req = request.post uploadUrl, ( err, res, body ) ->
-						if err
-							throw err
-
-						saveAudioParams = JSON.parse body
-						saveAudioParams.artist = item.artist
-						saveAudioParams.title = item.title
-
-						vkApi.request
-							method: "audio.save"
-							data: saveAudioParams
-							callback: ( result ) ->
-								item.id = result.response.id
-								item.isSynced = yes
-								item.hasConflict = no
-								item.shouldRemove = no
-								saveSyncedList folder, ->
-									callback item
-
-					filename = path.join folder, item.filename
-					req.form().append "file", fs.createReadStream filename
+		upload = ( item, folder, callback, onProgress = ( -> ), onStart = -> ) ->
+			path = require "path"
+			fs = require "fs"
+
+			filename = path.join folder, item.filename
+			fs.stat filename, ( err, stats ) ->
+				item.size = stats.size
+				vkApi.request
+					method: "audio.getUploadServer"
+					callback: ( result ) ->
+						uploadUrl = result.response.upload_url
+
+						item.isSyncing = yes
+						onStart item
+
+						# See: matlus.com/html5-file-upload-with-progress
+
+						formData = new FormData()
+						formData.append "file", new File filename, filename
+						xhr = new XMLHttpRequest()
+
+						xhr.upload.addEventListener "progress", ( evt ) ->
+							item.progress = evt.loaded
+							item.percentage = ( item.progress / item.size ) * 100
+							onProgress item
+
+						xhr.addEventListener "load", ( evt ) ->
+							saveAudioParams = JSON.parse evt.target.responseText
+							saveAudioParams.artist = item.artist
+							saveAudioParams.title = item.title
+
+							vkApi.request
+								method: "audio.save"
+								data: saveAudioParams
+								callback: ( result ) ->
+									item.id = result.response.id
+									item.isSyncing = no
+									item.isSynced = yes
+									item.hasConflict = no
+									item.shouldRemove = no
+									saveSyncedList folder, ->
+										callback item
+
+						xhr.addEventListener "error", ( evt ) ->
+							throw evt
+
+						xhr.open "POST", uploadUrl
+						xhr.send formData
 		download = ( item, folder, callback, onProgress = ( -> ), onStart = -> ) ->
 			request = require "request"
 			path = require "path"


Add capitalize first method to utils.

Add capitalize first method to utils.

--- src/utils.coffee
+++ src/utils.coffee
@@ -25,3 +25,12 @@
 module.exports.addAsObjectMember = (ctx, container, key, value) ->
     ctx[container] = ctx[container] || {}
     ctx[container][key] = value
+
+#
+# Capitalizes first letter
+#
+# @param [String] string
+# @return [String]
+#
+module.exports.capitalizeFirst = (string) ->
+    string.charAt(0).toUpperCase() + string.slice(1)