WIP: fix up error handling and account for possibility of account already existing.

--- services/web/app/coffee/Features/Subscription/RecurlyWrapper.coffee
+++ services/web/app/coffee/Features/Subscription/RecurlyWrapper.coffee
@@ -12,7 +12,32 @@
 	_createPaypalSubscription: (user, subscriptionDetails, recurly_token_id, callback) ->
 		logger.log {user_id: user._id, recurly_token_id}, "starting process of creating paypal subscription"
 		Async.waterfall([
-				(next) ->  # create account
+				(next) ->  # check if account exists
+					logger.log {user_id: user._id, recurly_token_id}, "checking if recurly account exists for user"
+					RecurlyWrapper.apiRequest({
+						url:    "accounts/#{user._id}"
+						method: "GET"
+					}, (error, response, responseBody) ->
+						if error
+							logger.error {error, user_id: user._id, recurly_token_id}, "error response from recurly while checking account"
+							return next(error)
+						result = {userExists: true}
+						if response.statusCode == 404
+							result.userExists = false
+							return next(null, result)
+						logger.log {user_id: user._id, recurly_token_id}, "user appears to exist in recurly"
+						RecurlyWrapper._parseAccountXml responseBody, (err, account) ->
+							if err
+								logger.error {err, user_id: user._id, recurly_token_id}, "error parsing account"
+								return next(err)
+							result.account = account
+							return next(null, result)
+					)
+
+				, (result, next) ->  # create account
+					if !result.userExists
+						logger.log {user_id: user._id, recurly_token_id}, "user already exists in recurly"
+						return next(null, result)
 					logger.log {user_id: user._id, recurly_token_id}, "creating user in recurly"
 					address = subscriptionDetails.address
 					if !address
@@ -38,14 +63,17 @@
 						method : "POST"
 						body   : requestBody
 					}, (error, response, responseBody) =>
-						return next(error) if error?
+						if error
+							logger.error {error, user_id: user._id, recurly_token_id}, "error response from recurly while creating account"
+							return next(error)
 						RecurlyWrapper._parseAccountXml responseBody, (err, account) ->
 							if err
 								logger.error {err, user_id: user._id, recurly_token_id}, "error creating account"
 								return next(err)
-							result = {account}
+							result.account = account
 							return next(null, result)
 					)
+
 				, (result, next) ->  # create billing info
 					logger.log {user_id: user._id, recurly_token_id}, "creating billing info in recurly"
 					accountCode = result?.account?.account_code
@@ -61,7 +89,9 @@
 						method: "POST"
 						body: requestBody
 					}, (error, response, responseBody) =>
-						return next(error) if error?
+						if error
+							logger.error {error, user_id: user._id, recurly_token_id}, "error response from recurly while creating billing info"
+							return next(error)
 						RecurlyWrapper._parseBillingInfoXml responseBody, (err, billingInfo) ->
 							if err
 								logger.error {err, user_id: user._id, accountCode, recurly_token_id}, "error creating billing info"
@@ -69,6 +99,7 @@
 							result.billingInfo = billingInfo
 							return next(null, result)
 					)
+
 				, (result, next) ->  # set address
 					logger.log {user_id: user._id, recurly_token_id}, "setting billing address in recurly"
 					accountCode = result?.account?.account_code
@@ -92,7 +123,9 @@
 						method: "PUT"
 						body: requestBody
 					}, (error, response, responseBody) =>
-						return next(error) if error?
+						if error
+							logger.error {error, user_id: user._id, recurly_token_id}, "error response from recurly while setting address"
+							return next(error)
 						RecurlyWrapper._parseBillingInfoXml responseBody, (err, billingInfo) ->
 							if err
 								logger.error {err, user_id: user._id, recurly_token_id}, "error updating billing info"
@@ -100,6 +133,7 @@
 							result.billingInfo = billingInfo
 							return next(null, result)
 					)
+
 				, (result, next) ->  # create subscription
 					logger.log {user_id: user._id, recurly_token_id}, "creating subscription in recurly"
 					requestBody = """
@@ -117,7 +151,9 @@
 						method : "POST"
 						body   : requestBody
 					}, (error, response, responseBody) =>
-						return next(error) if error?
+						if error
+							logger.error {error, user_id: user._id, recurly_token_id}, "error response from recurly while creating subscription"
+							return next(error)
 						RecurlyWrapper._parseSubscriptionXml responseBody, (err, subscription) ->
 							if err
 								logger.error {err, user_id: user._id, recurly_token_id}, "error creating subscription"
@@ -125,6 +161,7 @@
 							result.subscription = subscription
 							return next(null, result)
 					)
+
 			], (err, result) ->
 				if err
 					logger.error {err, user_id: user._id, recurly_token_id}, "error in paypal subscription creation process"


only set compileExited on compile errors

--- services/web/public/coffee/ide/pdf/controllers/PdfController.coffee
+++ services/web/public/coffee/ide/pdf/controllers/PdfController.coffee
@@ -118,6 +118,7 @@
 			$scope.pdf.renderingError = false
 			$scope.pdf.projectTooLarge = false
 			$scope.pdf.compileTerminated = false
+			$scope.pdf.compileExited = false

 			# make a cache to look up files by name
 			fileByPath = {}
@@ -144,7 +145,6 @@
 				fetchLogs(fileByPath)
 			else if response.status in ["validation-fail", "validation-pass"]
 				$scope.pdf.view = 'pdf'
-				$scope.pdf.compileExited = true
 				$scope.pdf.url = last_pdf_url
 				$scope.shouldShowLogs = true
 				fetchLogs(fileByPath, { validation: true })


test fix

test fix

--- tests/loader/start.coffee
+++ tests/loader/start.coffee
@@ -174,7 +174,8 @@
                     manifest: "./manifest.json"
                     index: "./index.html"
                     assets:
-                        template: /assets/index.tmpl
+                        index_template: /assets/index.tmpl
+                        appcache_template: /assets/appcache.tmpl
                         loader: /assets/loader.js
                         md5: /assets/md5.js
                         fake_app: /assets/fake/app.js
@@ -205,7 +206,8 @@
                     manifest: "./manifest.json"
                     index: "./index.html"
                     assets:
-                        template: /assets/index.tmpl
+                        index_template: /assets/index.tmpl
+                        appcache_template: /assets/appcache.tmpl
                         loader: /assets/loader.js
                         md5: /assets/md5.js
                         fake_app: /assets/fake/app.js


Updated to latest naming

Updated to latest naming

--- rest_controller.coffee
+++ rest_controller.coffee
@@ -137,4 +137,4 @@

     options = (if @renderOptions then @renderOptions(req, template_name) else {})
     models = if _.isArray(json) then _.map(json, (model_json) => new @model_type(@model_type::parse(model_json))) else new @model_type(@model_type::parse(json))
-    JSONUtils.renderJSON models, template, options, callback
+    JSONUtils.renderTemplate models, template, options, callback


Fix minor issue when autocompleting static class names.

--- lib/autocompletion/member-provider.coffee
+++ lib/autocompletion/member-provider.coffee
@@ -98,7 +98,7 @@
                 # Ensure we don't get very long return types by just showing the last part.
                 snippet = null
                 displayText = word
-                returnValueParts = if ele.args.return.type then ele.args.return.type.split('\\') else []
+                returnValueParts = if ele.args.return?.type then ele.args.return.type.split('\\') else []
                 returnValue = returnValueParts[returnValueParts.length - 1]

                 if ele.isMethod


movement fixes for firefox

movement fixes for firefox

--- oriDomi.coffee
+++ oriDomi.coffee
@@ -494,8 +494,6 @@
     # Attach touch/drag event listeners.
     @stageEl.addEventListener 'touchstart', @_onTouchStart, false
     @stageEl.addEventListener 'mousedown', @_onTouchStart, false
-    @stageEl.addEventListener 'touchmove', @_onTouchMove, false
-    @stageEl.addEventListener 'mousemove', @_onTouchMove, false
     @stageEl.addEventListener 'touchend', @_onTouchEnd, false
     @stageEl.addEventListener 'mouseup', @_onTouchEnd, false

@@ -709,7 +707,7 @@
     # If the speed value is `true` reset the speed to the original settings.
     # Set it to zero if `false`.
     if typeof speed is 'boolean'
-      speed = if speed then @settings.speed + 'ms' else '0'
+      speed = if speed then @settings.speed + 'ms' else '0ms'

     # To loop through the shaders, derive the correct pair from the current anchor.
     if @lastAnchor is 'left' or @lastAnchor is 'right'
@@ -776,6 +774,10 @@
     else
       @["_#{ @_touchAxis }1"] = e.targetTouches[0]["page#{ @_touchAxis.toUpperCase() }"]

+    # Add movement listener.
+    @stageEl.addEventListener 'touchmove', @_onTouchMove, false
+    @stageEl.addEventListener 'mousemove', @_onTouchMove, false
+
     # Return that value to an external listener.
     @settings.touchStartCallback @["_#{ @_touchAxis }1"]

@@ -785,9 +787,7 @@
     return unless @_touchEnabled
     e.preventDefault()
     # Set a reference to the current x or y position.
-    # Cancel if the mouse button isn't down.
     if e.type is 'mousemove'
-      return if e.which isnt 1
       current = e["page#{ @_touchAxis.toUpperCase() }"]
     else
       current = e.targetTouches[0]["page#{ @_touchAxis.toUpperCase() }"]
@@ -815,6 +815,11 @@
     return unless @_touchEnabled
     # Enable tweening again.
     @_setTweening true
+
+    # Remove movement listeners.
+    @stageEl.removeEventListener 'touchmove', @_onTouchMove, false
+    @stageEl.removeEventListener 'mousemove', @_onTouchMove, false
+
     # Pass callback final value.
     @settings.touchEndCallback @["_#{ @_touchAxis }Last"]


support for more expression string conversion types (multip, subexps)

--- math/expression_to_string_spec.coffee
+++ math/expression_to_string_spec.coffee
@@ -23,10 +23,15 @@
       expect(@to_string(exp)).toEqual "10 ^ "
       expect(@to_html_string(exp)).toEqual "10 ˆ "

-  it "displays correctly for multiplication", ->
-    exp = @builder(10, '*',  10)
-    expect(@to_string(exp)).toEqual "10 * 10"
-    expect(@to_html_string(exp)).toEqual "10 × 10"
+  describe "multiplication", ->
+    it "", ->
+      exp = @builder(10, '*',  10)
+      expect(@to_string(exp)).toEqual "10 * 10"
+      expect(@to_html_string(exp)).toEqual "10 × 10"

-
+  describe "sub-expressions", ->
+    it "displays them as parentheses", ->
+      exp = @builder([10, '*',  10])
+      expect(@to_string(exp)).toEqual "( 10 * 10 )"
+      expect(@to_html_string(exp)).toEqual "( 10 × 10 )"


activity: apply codestyle

activity: apply codestyle

--- client/activity/lib/components/createprivatechannelmodal/index.coffee
+++ client/activity/lib/components/createprivatechannelmodal/index.coffee
@@ -115,20 +115,20 @@
     if @state.preExistingChannel
       if @state.name or @state.purpose
         props = _.assign {}, props,
-          buttonExtraTitle: continueButtonTitle
-          onButtonExtraClick: continueButtonOnClick
-          buttonConfirmTitle: createButtonTitle
-          buttonConfirmClassName: 'Button--cancel'
+          buttonExtraTitle       : continueButtonTitle
+          onButtonExtraClick     : continueButtonOnClick
+          buttonConfirmTitle     : createButtonTitle
+          buttonConfirmClassName : 'Button--cancel'
       else
         props = _.assign {}, props,
-          buttonConfirmTitle: continueButtonTitle
-          onConfirm: continueButtonOnClick
+          buttonConfirmTitle : continueButtonTitle
+          onConfirm          : continueButtonOnClick
     else
       props = _.assign {}, props,
-        buttonExtraTitle: null
-        buttonConfirmTitle: createButtonTitle
-        buttonOnClick: @bound 'createChannel'
-        buttonConfirmClassName: 'Button--primary'
+        buttonExtraTitle       : null
+        buttonConfirmTitle     : createButtonTitle
+        buttonOnClick          : @bound 'createChannel'
+        buttonConfirmClassName : 'Button--primary'

     return props


Restore custom TAGP redirects

Restore custom TAGP redirects

These were hard-coded in the old app's index.coffee, but
probably make more sense here. (In fact, one was here already
for /genes)

--- lib/middleware/hardcoded_redirects.coffee
+++ lib/middleware/hardcoded_redirects.coffee
@@ -15,6 +15,8 @@
   '/institution': '/institutions'
   '/filter/artworks': '/browse'
   '/filter/artworks/*': '/browse'
+  '/category': '/categories'
+  '/gene': '/categories'
   '/genes': '/categories'
   '/partner-application': '/apply'
   '/fair-application': '/apply/fair'


Added context parameter

Added context parameter

--- src/bodyguard.coffee
+++ src/bodyguard.coffee
@@ -2,7 +2,7 @@

   noop = ->

-  bodyguard = (definition, obj) ->
+  bodyguard = (definition, obj, context) ->
     result = {}

     if Array.isArray definition


tests refactoring 1

tests refactoring 1

--- tests/test_builder.coffee
+++ tests/test_builder.coffee
@@ -214,31 +214,26 @@

             manifest = JSON.parse(fs.readFileSync("/testimonial/manifest.json", encoding: "utf8"))

-            expect(manifest)
-                .to.have.property('modules')
-                .to.be.an("Array").with.length(4)
-
-            expect(manifest.modules[0]).to.have.properties
+            expect(manifest).to.have.properties
+                modules: -> @that.is.an("Array").with.length(4).and.properties
+                    0: -> @that.have.properties
                 id: -> @that.equals("c")
                 deps: -> @that.deep.equals({})
                 type: -> @that.equals(type)
                 url: -> @that.equals("http://127.0.0.1:8010/module1/a/c.js")
-
-            expect(manifest.modules[1]).to.have.properties
+                    1: -> @that.have.properties
                 id: -> @that.equals("b")
                 deps: -> @that.deep.equals
                     "./a/c": "c"
                 type: -> @that.equals(type)
                 url: -> @that.equals("http://127.0.0.1:8010/module1/b.js")
-
-            expect(manifest.modules[2]).to.have.properties
+                    2: -> @that.have.properties
                 id: -> @that.equals("d")
                 deps: -> @that.deep.equals
                     "a1/c": "c"
                 type: -> @that.equals(type)
                 url: -> @that.equals("http://127.0.0.1:8010/module2/d.js")
-
-            expect(manifest.modules[3]).to.have.properties
+                    3: -> @that.have.properties
                 id: -> @that.equals("e")
                 deps: -> @that.deep.equals
                     "a1/../b": "b"


use escape instead of encodeURIComponent

--- scripts/twitter-oauth.coffee
+++ scripts/twitter-oauth.coffee
@@ -50,9 +50,9 @@
     for kv in options
       if kv[1]? && kv[1] != ""
         if hasQ
-          st.push "#{kv[0]}=\"#{encodeURIComponent(kv[1])}\""
+          st.push "#{kv[0]}=\"#{escape(kv[1])}\""
         else
-          st.push "#{kv[0]}=#{encodeURIComponent(kv[1])}"
+          st.push "#{kv[0]}=#{escape(kv[1])}"
     st.join(joiner)

   oauth_params: (sources...)->
@@ -80,8 +80,8 @@
   signature: (sign_data = [], url='', method = 'POST', csonly = false) ->
     sign_data = @sort_options sign_data
     sign_data = @join_options sign_data, '&', false
-    sb = "#{method.toUpperCase()}&"+encodeURIComponent(url)
-    sb = sb + "&" + encodeURIComponent(sign_data)
+    sb = "#{method.toUpperCase()}&"+escape(url)
+    sb = sb + "&" + escape(sign_data)
     key = @consumer_secret_key+"&"
     unless csonly
       key += @access_token_secret


FullScreenApp now works a little better in Safari on mobile (the keyboard kept scrolling the main window down 4 pixels)

--- src/art/engine/full_screen_app.coffee
+++ src/art/engine/full_screen_app.coffee
@@ -91,6 +91,7 @@
             margin: 0px;
             background-color: #eee;
             overflow: hidden;
+            font-size: 0px;
           }
           canvas {
             width: 100%;


collision

collision

--- src/collision.coffee
+++ src/collision.coffee
@@ -23,7 +23,8 @@
        height: 10

      Collision.rectangular(player, enemy)
-  => true
+  => true
+
      Collision.rectangular(player, {x: 50, y: 40, width: 30, height: 30})
   => false


fixed popups

fixed popups

--- lib/atox-PopUp.coffee
+++ lib/atox-PopUp.coffee
@@ -26,20 +26,21 @@

   add: (type, name, content, img) ->
     temp = new PopUp( @currentID, type, name, content, img )
+    @currentID++
+
     temp.hide()
     temp.appendTo this
-    temp.fadeIn atom.config.get 'atox.fadeDuration'
-
-    @PopUps.push temp
+    temp.fadeIn   atom.config.get 'atox.fadeDuration'

     setTimeout =>
        @shift()
     , ( atom.config.get 'atox.popupTimeout' ) * 1000

-    @currentID++
-
+    @PopUps.push temp

   shift: ->
-    @PopUps[0].fadeOut (atom.config.get 'atox.fadeDuration'), =>
-       @PopUps[0].remove()
-       @PopUps.shift()
+    temp = @PopUps[0]
+    @PopUps.shift()
+
+    temp.fadeOut (atom.config.get 'atox.fadeDuration'), =>
+       temp.remove()


disabling actions once save or published is clicked

--- src/components/task-plan/footer.cjsx
+++ src/components/task-plan/footer.cjsx
@@ -59,6 +59,7 @@
           isWaiting={isWaiting and @state.publishing}
           isFailed={isFailed}
           waitingText='Publishing…'
+          disabled={isWaiting}
           >
           {'Publish'}
         </AsyncButton>
@@ -75,6 +76,7 @@
             isWaiting={isWaiting and @state.saving}
             isFailed={isFailed}
             waitingText='Saving…'
+            disabled={isWaiting}
             >
             {'Save as Draft'}
           </AsyncButton>
@@ -95,7 +97,7 @@

     <div className='footer-buttons'>
       {publishButton}
-      <BS.Button aria-role='close' onClick={@onCancel}>Cancel</BS.Button>
+      <BS.Button aria-role='close' disabled={isWaiting} onClick={@onCancel}>Cancel</BS.Button>
       {saveLink}
       <BS.OverlayTrigger trigger='click' placement='top' overlay={tips} rootClose={true}>
         <BS.Button className="footer-instructions" bsStyle="link">


Fixed documentation string

Fixed documentation string

--- lib/characters.coffee
+++ lib/characters.coffee
@@ -116,7 +116,7 @@
       return char

   # Find the index of a character
-  # parma
+  # @param character [Character] a character to find index
   indexOf: (character) ->
     for char, index in @_model.val()
       if char == character


boot sclang - default only for now

boot sclang - default only for now

--- lib/controller.coffee
+++ lib/controller.coffee
@@ -1,16 +1,17 @@
 PostWindow = require './post-window'
 Bacon = require('baconjs')
 url = require('url')
-# SuperColliderJS = require 'supercolliderjs'
-# Session = require './session'
+SuperColliderJS = require 'supercolliderjs'

 module.exports =
 class Controller

   constructor: (@workspaceView, directory) ->
+    @directory = directory.path
     @postWindows = {}
     @busses = {}
+    @sclangs = {}
     @activeURI = null
     @defaultURI = "sclang://localhost:57120"

@@ -26,6 +27,8 @@
       catch error
         return
       return unless protocol is 'sclang:'
+
+      @startSCLang(uriToOpen)

       # returns it
       @createPostWindow(uriToOpen)
@@ -67,6 +70,21 @@
       @busses[uri] = bus
     bus

+  startSCLang: (uri) ->
+    opts = @getPreferences()
+    opts.stdin = false
+    opts.echo = false
+    sclang = new SuperColliderJS.sclang(opts)
+    sclang.boot()
+
+    bus = @busForURI(uri)
+    sclang.on 'stdout', (d)->
+      bus.push("
#{d}
")
+    sclang.on 'stderr', (d)->
+      bus.push("
#{d}
")
+
+    @sclangs[uri] = sclang
+
   currentExpression: ->
     editor = atom.workspace.getActiveEditor()
     return unless editor?


Retira default $placeholder

Retira default $placeholder

--- src/coffee/portal-sku-selector.coffee
+++ src/coffee/portal-sku-selector.coffee
@@ -5,7 +5,7 @@
 # Sku Selector elements function.
 #

-$.skuSelector = { $placeholder: undefined }
+$.skuSelector = {}

 # Renders the Sku Selector inside the given placeholder
 # Usage:


don't process a click until quiescent

don't process a click until quiescent

--- dwim.coffee
+++ dwim.coffee
@@ -240,6 +240,8 @@
   mouseCB: (what, where) =>
     if what != 'click'
       return
+    if @gfx.isAnimating()
+      return

     handled = false


Guard against string element in callAttachHook

--- src/space-pen.coffee
+++ src/space-pen.coffee
@@ -193,7 +193,7 @@

 # Trigger attach event when views are added to the DOM
 callAttachHook = (element) ->
-  return unless element
+  return unless element instanceof jQuery
   onDom = element.isOnDom?()

   elementsWithHooks = []


Fix FMC-453 - Profiles page needs initial loading widget

--- fabric/fmc-webui/src/main/webapp/app/controllers/profiles_page.coffee
+++ fabric/fmc-webui/src/main/webapp/app/controllers/profiles_page.coffee
@@ -24,6 +24,7 @@
   "controllers/controls/validating_text_input"
   "controllers/controls/collection"
   "controllers/controls/swappable_model_view"
+  "controllers/controls/loading_page"
 ], (app, jade, Versions, Profile) ->

@@ -529,6 +530,7 @@

   app.router.route "/versions", "versions", ->
+    app.page new FON.LoadingPage
     app.versions.fetch
       success: (model, resp) =>
         if !app.session_state.get "selected_version"


Added a method to check the existence of a doc.

--- src/Collections.coffee
+++ src/Collections.coffee
@@ -300,6 +300,15 @@
       exists
     if options?.returnMap then idMap else docs

+  # @param {String|Meteor.Collection|Cursor} arg
+  # @param {String} id - A document ID.
+  # @returns {Boolean} Whether the given document ID exists in the given collection.
+  hasDoc: (arg, id) ->
+    unless id? then throw new Error('No document ID provided')
+    collection = @get(arg)
+    unless collection then throw new Error('No collection provided')
+    collection._collection._docs.has(id)
+
 ####################################################################################################
 # VALIDATION
 ####################################################################################################


Add default option value to batch docs

Add default option value to batch docs

--- src/leveldb/batch.coffee
+++ src/leveldb/batch.coffee
@@ -96,8 +96,8 @@

       @param {Object} [options] Optional options. See also the
         `leveldb/options.h` header file.
-        @param {Boolean} [options.sync] Set to truthy value to flush write
-          to disk before returning.
+        @param {Boolean} [options.sync=false] Set to truthy value to flush
+          write to disk before returning.
       @param {Function} [callback] Optional callback.

   ###


Fix binding a folder using the wrong id.

--- src/application.coffee
+++ src/application.coffee
@@ -146,7 +146,7 @@
 				Privileged: true
 				PortBindings: ports
 				Binds: [
-					'/resin-data/' + app.id + ':/data'
+					'/resin-data/' + app.appId + ':/data'
 					'/lib/modules:/lib/modules'
 					'/var/run/docker.sock:/run/docker.sock'
 				]


Add a clarification about the usage of addOpener

Documentation augmented with a note about the need of using fake protocol in
order to invoke opener on a file already opened.

See discussion at

https://discuss.atom.io/t/difficulties-using-atom-workspace-addopener/20444

--- src/workspace.coffee
+++ src/workspace.coffee
@@ -506,6 +506,12 @@
   #
   # Returns a {Disposable} on which `.dispose()` can be called to remove the
   # opener.
+  #
+  # Note that the opener will be called if and only if the URI is not already open
+  # in the current pane. The searchAllPanes flag expands the search from the
+  # current pane to all panes. If the file path is converted from a standard path,
+  # e.g. /foo/bar/baz.html, to a URI with a fake protocol, e.g. custom://foo/bar/baz.html,
+  # it wouldn't conflict and the opener will be invoked.
   addOpener: (opener) ->
     if includeDeprecatedAPIs
       packageName = @getCallingPackageName()


refactor(relations): module export + relation models can now be functions

--- src/lib/relatable.coffee
+++ src/lib/relatable.coffee
@@ -1,3 +1,11 @@
+if typeof require == 'function'
+  object_merge = require './utils'
+  Helpers      = require '../helpers'
+
+  Observables     = require './observable'
+  Observable      = Observables.Observable
+  ObservableArray = Observables.ObservableArray
+
 Relatable = (self, options) ->
   # Set relations
   # API wise has_one and belongs_to are the same, so we merge them
@@ -23,11 +31,13 @@
       object_merge data,
         model: name.capitalize()

-      relation_params = relationship_fields name, data.model, self.api(), options
+      relation_params = Helpers.relationship_fields name, data.model, self.api(), options

       fn = window[data.model]
+      fn = data.model if data.model? and typeof data.model == 'function'

       if typeof fn != 'function'
+        console.debug 'Relation model for "' + name + '" not a function: ', fn
         throw new Error 'No model for has_one/belongs_to relation found'

       if typeof self[name + '_id'] != 'function' && name not in options.belongs_to
@@ -43,7 +53,7 @@
         Observable self, name, new fn()
         #LazyObservable self, name, lazy_single_get_fn, relation_params

-      self[name + '_get'] = single_get_fn.apply self, relation_params
+      self[name + '_get'] = Helpers.single_get_fn.apply self, relation_params

   # Multiple relations
   # Generated methods:
@@ -66,7 +76,7 @@
       object_merge data,
         model: name.capitalize()

-      relation_params = relationship_fields name, data.model, self.api(), options
+      relation_params = Helpers.relationship_fields name, data.model, self.api(), options

       # Get
       #
@@ -81,12 +91,14 @@
       else
         ObservableArray self, name + 's', []
         #LazyObservableArray self, name + 's', lazy_get_fn, relation_params
-      self[name + '_get'] = get_fn.apply self, relation_params
+      self[name + '_get'] = Helpers.get_fn.apply self, relation_params

       # Create
       #
-      self[name + '_add'] = create_fn.apply self, relation_params
+      self[name + '_add'] = Helpers.create_fn.apply self, relation_params

       # Destroy
       #
-      self[name + '_destroy'] = destroy_fn.apply self, relation_params
+      self[name + '_destroy'] = Helpers.destroy_fn.apply self, relation_params
+
+module.exports = Relatable if module?


Call `newedFile` event after new-in-folder

--- src/code/client.coffee
+++ src/code/client.coffee
@@ -252,6 +252,7 @@
           id: folder

       @_ui.editInitialFilename()
+    @_event 'newedFile', {content: ""}

   setInitialFilename: (filename) ->
     @state.metadata.name = filename


put the instance to a failed state when we receive startup failure logs

--- imports/server/stateReconciler.coffee
+++ imports/server/stateReconciler.coffee
@@ -90,6 +90,10 @@

   updateStartupFailedLogs: (instanceName, logData) ->
     updateStartupLogs instanceName, logData
+    Instances.update {name: instanceName},
+      $set:
+        state: 'failed'
+        status: 'This instance could not be started because of errors.'

   updateTeardownLogs: (instanceName, logData) ->
     Instances.update {name: instanceName},


parseInt the day if not a range

parseInt the day if not a range

--- lib/olson/zone.coffee
+++ lib/olson/zone.coffee
@@ -41,6 +41,8 @@
             # Check for day ranges: Sun>=1
             if helpers.Months.IsDayOfMonthRule day
                 day = helpers.Months.DayOfMonthByRule day, year, month
+            else
+                day = parseInt day, 10

             standardTime = helpers.Time.StandardTimeToUTC @offset, year, month, day, h, mi, s
             # The end time is not inclusive, so back off by 1 millisecond


setting email updates and no duplicate button injection

--- shared/js/src/content.coffee
+++ shared/js/src/content.coffee
@@ -97,7 +97,7 @@

     initTrackJs: ->
       @getVersion().then (version) =>
-        @getCurrentGmail().then (email) ->
+        @getCurrentEmailAddress().then (email) ->
           opts =
             userId: email
             version: version
@@ -213,7 +213,7 @@
          logged in user
          @returns {Promise} a promise that resolves to the email address {String}
     ###
-    getCurrentGmail: -> currentEmailAddressDeferred.promise
+    getCurrentEmailAddress: -> currentEmailAddressDeferred.promise

     ###
     Sets up a listener for GMailr
@@ -283,12 +283,18 @@
     ###
     inject: =>
       return unless @inConversation()
-      @getSettingEmail().then (settingEmail) =>
+
+      log 'inject: in a conversation'
+      Q.all([
+        @getSettingEmail()
+        @getCurrentEmailAddress()
+      ])
+      .then ([settingEmail, currentEmailAddress]) =>
         log 'Email address in settings', settingEmail
-        @getCurrentGmail().then (currentGmail) =>
-          log 'Current Gmail window', currentGmail
-          @injectThread() if (not settingEmail or not currentGmail) or currentGmail in settingEmail.split /[, ]+/ig
-      return
+        log 'Current Gmail window', currentEmailAddress
+        if (not settingEmail or not currentEmailAddress) or currentEmailAddress in settingEmail.split /[, ]+/ig
+          return @injectThread()
+        return

     isPreviewPaneEnabled: ->
       ($ M.GM_SEL.PREVIEW_PANE_ENABLED).length > 0


no duplicate button injection

no duplicate button injection

--- shared/js/src/content.coffee
+++ shared/js/src/content.coffee
@@ -283,6 +283,8 @@
     ###
     inject: =>
       return unless @inConversation()
+      return if @inject.lock
+      @inject.lock = true

       log 'inject: in a conversation'
       Q.all([
@@ -294,6 +296,10 @@
         log 'Current Gmail window', currentEmailAddress
         if (not settingEmail or not currentEmailAddress) or currentEmailAddress in settingEmail.split /[, ]+/ig
           return @injectThread()
+        return
+      .then =>
+        log 'releasing inject lock'
+        @inject.lock = false
         return

     isPreviewPaneEnabled: ->


show monday as first day in calendar

show monday as first day in calendar

--- shared/js/src/content.coffee
+++ shared/js/src/content.coffee
@@ -490,6 +490,7 @@
             pickerMenu.onShow = ->
               unless picker
                 picker = new Pikaday
+                  firstDay: 1
                   bound: false
                   format: dateFormat
                   minDate: moment().add(1, 'day').toDate()


* update unit testing

* update unit testing


constructor checks for oriDomiSupport

constructor checks for oriDomiSupport

--- oriDomi.coffee
+++ oriDomi.coffee
@@ -72,7 +72,7 @@
   css[key] = testProp value
   if !css[key]
     console?.warn 'oriDomi: Browser does not support oriDomi'
-    oridomiSupport = false
+    oriDomiSupport = false
     break

 css.gradientProp = getGradientProp()


added bleed to vPanel

added bleed to vPanel

--- oriDomi.coffee
+++ oriDomi.coffee
@@ -269,7 +269,7 @@

     vPanel = hPanel.cloneNode false
     vPanel.className = 'oridomi-panel-v'
-    vPanel.style.width = @panelWidth + 'px'
+    vPanel.style.width = @panelWidth + bleed + 'px'
     vPanel.style.height = '100%'
     vPanel.style[css.origin] = 'left'
     vPanel.appendChild vMask


create jQuery object at end of constructor

--- oriDomi.coffee
+++ oriDomi.coffee
@@ -113,7 +113,6 @@
       return devMode and console.warn 'oriDomi: First argument must be a DOM element'

     {@shading, @shadingIntensity, @vPanels, @hPanels} = @settings
-    @$el = $ @el if $
     elStyle = root.getComputedStyle @el

     @width = parseInt(elStyle.width, 10) +
@@ -310,6 +309,7 @@
       @el.style.display = 'block'
       @el.style.visibility = 'visible'

+    @$el = $ @el if $
     @_callback @settings
     devMode and console.timeEnd 'oridomiConstruction'


revised event listener process

revised event listener process

--- oridomi.coffee
+++ oridomi.coffee
@@ -491,11 +491,19 @@

     # Create an element to hold stages.
     @stageEl = document.createElement 'div'
-    # Attach touch/drag event listeners.
-    @stageEl.addEventListener 'touchstart', @_onTouchStart, false
-    @stageEl.addEventListener 'mousedown', @_onTouchStart, false
-    @stageEl.addEventListener 'touchend', @_onTouchEnd, false
-    @stageEl.addEventListener 'mouseup', @_onTouchEnd, false
+    # Array of event type pairs.
+    eventPairs = [['TouchStart', 'MouseDown'], ['TouchEnd', 'MouseUp'],
+                  ['TouchMove', 'MouseMove'], ['TouchLeave', 'MouseLeave']]
+    # Detect native `mouseleave` support.
+    mouseLeaveSupport = 'onmouseleave' of window
+    # Attach touch/drag event listeners in related pairs.
+    for eventPair in eventPairs
+      for eString in eventPair
+        unless eString is 'TouchLeave' and not mouseLeaveSupport
+          @stageEl.addEventListener eString.toLowerCase(), @['_on' + eventPair[0]], false
+        else
+          @stageEl.addEventListener 'mouseout', @['_onMouseOut'], false
+          break

     @enableTouch() if @settings.touchEnabled


Handle items as returned from the new tables.

--- src/coffee/controllers/history.coffee
+++ src/coffee/controllers/history.coffee
@@ -150,7 +150,7 @@
           console.debug "replacing message data: #{ data }"
           @set ['messages', idx, 'data'], data
         else
-          @mines.then(atURL data.service.root)
+          @mines.then(atURL(data['service:base'] ? data.service.root))
                 .then(connectWithName)
                 .then (service) => @set ['messages', idx], {tool, data, service, kind: 'msg'}


fix wrong cursor position when insert empty style

If there is a '\n' in a style's {after} property, the cursor will be at
a wrong line (below the right one) after insertion.
For example, inserting a codeblock will leave the cursor at the last
line with '```'.
After this fix, the cursor will be between the two lines of '```',
namely, in the middle.

--- lib/style-text.coffee
+++ lib/style-text.coffee
@@ -36,9 +36,10 @@
     selection.insertText(text)

   insertEmptyStyle: (selection) ->
-    selection.insertText(@addStyle(""))
-    {row, column} = selection.cursor.getBufferPosition()
-    selection.cursor.setBufferPosition([row, column - @style.after.length])
+    selection.insertText(@style.before)
+    position = selection.cursor.getBufferPosition()
+    selection.insertText(@style.after)
+    selection.cursor.setBufferPosition(position)

   isStyleOn: (text) ->
     @getStylePattern().test(text) if text


Return early when no tags

Return early when no tags

--- lib/tag-finder.coffee
+++ lib/tag-finder.coffee
@@ -131,6 +131,8 @@
   # given fragment then the end point of that pair does not count as a matching
   # closing tag.
   tagDoesNotCloseInFragment: (tags, fragment) ->
+    return if tags.length is 0
+
     stack = tags
     stackLength = stack.length
     tag = _.escapeRegExp(tags[tags.length-1])


Fix tag updating when the build get's minified

Connects-To: #450
Change-Type: patch

--- lib/util/index.coffee
+++ lib/util/index.coffee
@@ -67,7 +67,7 @@
 	body: 'No application found to associate with the api key'

 exports.uniqueKeyViolated =
-	name: 'ResinRequestError'
+	code: 'ResinRequestError'
 	body: 'Unique key constraint violated'

 exports.treatAsMissingApplication = (nameOrId) ->


add debug message

add debug message

--- src/dockerhub.coffee
+++ src/dockerhub.coffee
@@ -70,16 +70,20 @@

     res.end ""

-  robot.respond /dockerhub\s+trigger\s+set\s*(\S+)\s+with\s+(\S+)$/, (res) ->
+  robot.respond /dockerhub\s+trigger\s+set\s+(\S+)\s+with\s+(\S+)\s*$/, (res) ->
+    robot.logger.debug("hubot-docker build-trigger set")
     repo = res.match(1)
     token = res.match(2)
+    robot.logger.debug("repo: #{repo}, token: #{token}")
     triger_tokens = (robot.brain.get("dockerhub-trigger-tokens") || {})
     triger_tokens[repo] = token
     robot.brain.set("dockerhub-trigger-tokens", triger_tokens)
     res.send "#{repo} のトークンを設定しました"

-  robot.respond /dockerhub\s+trigger\s+del\s*(\S+)$/, (res) ->
+  robot.respond /dockerhub\s+trigger\s+del\s+(\S+)\s*$/, (res) ->
+    robot.logger.debug("hubot-docker build-trigger del")
     repo = res.match(1)
+    robot.logger.debug("repo: #{repo}")
     triger_tokens = (robot.brain.get("dockerhub-trigger-tokens-repository-to-rooms") || {})
     if triger_tokens[repo]
       delete triger_tokens[repo]
@@ -88,13 +92,16 @@
     else
       res.send "#{repo} のトークンは設定されていません"

-  robot.respond /dockerhub\s+trigger\s+show$/, (res) ->
+  robot.respond /dockerhub\s+trigger\s+show\s*$/, (res) ->
+    robot.logger.debug("hubot-docker build-trigger show")
     triger_tokens = (robot.brain.get("dockerhub-trigger-tokens") || {})
     for repo, token of triger_tokens
       res.send "#{repo} の build trigger のトークンは #{token} です"

-  robot.respond /dockerhub\s+trigger\s+invoke\s+(\S+)$/, (res) ->
+  robot.respond /dockerhub\s+trigger\s+invoke\s+(\S+)\s*$/, (res) ->
+    robot.logger.debug("hubot-docker build-trigger invoke")
     repo = res.match(1)
+    robot.logger.debug("repo: #{repo}")
     triger_tokens = (robot.brain.get("dockerhub-trigger-tokens") || {})
     if token = triger_tokens[repo]
       res.send "#{repo} の build trigger を発火します"


Use get_value instead of call

Use get_value instead of call

--- src/draw_grid.coffee
+++ src/draw_grid.coffee
@@ -11,7 +11,7 @@
       fill: "#00aa00"
     }

-  grid_path = world.call('grid_path')
+  grid_path = world.get_value('grid_path')
   [
     SVG.rect {
       class: "grid-background"


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/expirable.coffee
+++ src/expirable.coffee
@@ -8,7 +8,7 @@

   self.bind "update", ->
     if I.fadeOut
-      log (I.age + 1)
+      log (I.age + 1 /
       I.alpha = 1 - (I.age + 1 / I.duration)

     if I.duration != -1 && I.age + 1 >= I.duration


Update code comment for the new response format

--- src/loaders/collection-loader.coffee
+++ src/loaders/collection-loader.coffee
@@ -60,12 +60,16 @@
     # The server response should look something like this:
     #  {
     #    count: 200,
-    #    page_number: 1,
-    #    page_count: 10,
     #    results: [{ key: "tasks", id: 10 }, { key: "tasks", id: 11 }],
     #    time_entries: [{ id: 2, title: "te1", project_id: 6, task_id: [10, 11] }]
     #    projects: [{id: 6, title: "some project", time_entry_ids: [2] }]
     #    tasks: [{id: 10, title: "some task" }, {id: 11, title: "some other task" }]
+    #    _meta: {
+    #      count: 200,
+    #      page_number: 1,
+    #      page_count: 10,
+    #      page_size: 20
+    #    }
     #  }
     # Loop over all returned data types and update our local storage to represent any new data.


Restore scan spec asserts now that leading space is gone

--- spec/app/project-spec.coffee
+++ spec/app/project-spec.coffee
@@ -247,17 +247,17 @@

         stdout = BufferedProcess.prototype.bufferStream.argsForCall[0][1]
         stdout ":#{fs.resolveOnLoadPath('fixtures/dir/a')}\n"
-        stdout "1;0 3:aaa bbb\n2;3 2:cc aa cc\n"
+        stdout "1;0 3: aaa bbb\n2;3 2: cc aa cc\n"

         expect(iterator.argsForCall[0][0]).toEqual
           path: project.resolve('a')
-          match: 'aa '
+          match: 'aaa'
           range: [[0, 0], [0, 3]]
           lineText: 'aaa bbb'

         expect(iterator.argsForCall[1][0]).toEqual
           path: project.resolve('a')
-          match: 'a '
+          match: 'aa'
           range: [[1, 3], [1, 5]]
           lineText: 'cc aa cc'


update b2b2c

update b2b2c

--- core/core_modules/b2b2c.coffee
+++ core/core_modules/b2b2c.coffee
@@ -75,6 +75,7 @@
         userAgent = process.env.AINETWORK_DEFAULT_UA || "otherbot/crawler"
         instance  = new OTHERWebInstance userAgent,rootURL,username,password
         instance.compileObject "biztrader",@b2b2c_trader_function
+        instance.loginStatus = "complete"
         return instance

     newStoreInstance : (rootURL,authType,authKey,authSecret)->


fixing choose

fixing choose

--- src/hubot-cah.coffee
+++ src/hubot-cah.coffee
@@ -154,8 +154,8 @@
 # @param answerIndex: if value outside db.answers array range, no winner this round
 # @return string for public display
 czar_choose_winner = (answerIndex) ->
-  responseString = "Current round:"
   if 0 <= answerIndex and answerIndex < db.answers.length
+    responseString = "Current round:"
     for answer in db.answers
       responseString += "\n#{answer[0]}: #{generate_phrase(db.blackCard, answer[1])}"

@@ -202,11 +202,10 @@
 module.exports = (robot) ->
   robot.error (err, res) ->
     if res?
-      res.reply "Someone broke me again: #{err.message}\n#{err.stack}\n#{robot.brain.data.cah}"
-    else
-      robot.logger.error err.message
-      robot.logger.error err.stack
-      robot.logger.error robot.brain.data.cah
+      res.reply "Someone broke me again: #{err.message}"
+    robot.logger.error err.message
+    robot.logger.error err.stack
+    robot.logger.error JSON.stringify(robot.brain.data.cah, null, '\t'))

   robot.brain.on "loaded", =>
     if !robot.brain.data.cah
@@ -233,7 +232,7 @@
         res.send responseString
     else
       res.reply "NOPE, not everyone has responded yet! (#{status})\n(Czars can use 'cah answers!' to see answers early)"
-
+
   # combo hear and respond, prepends ^ to hear regex
   # good for allowing same commands in room and DM
   robot.hearspond = (regex, cb) ->
@@ -348,7 +347,7 @@
       res.reply "Whoa easy only the czar can force out the answers"
       return
     show_answers res, 'force, mother fucker'
-
+

   robot.hear /^cah (choose|pick) (\d+)$/i, (res) ->
     if sender(res) != db.czar
@@ -356,7 +355,7 @@
     else if db.answers.length == 0
       res.reply "No submissions to choose from yet."
     else
-      num = parseInt(res.match[1]) - 1
+      num = parseInt(res.match[2]) - 1
       if num < 0 or num >= db.answers.length
         res.reply "That is not an valid choice, try again."
       else
@@ -367,3 +366,6 @@

   robot.hear /^cah skip$/i, (res) ->
     res.send czar_choose_winner -1
+
+  robot.hearspond /cah debug$/i, (res) ->
+    res.send "Here's some json for you\n" + JSON.stringify(robot.brain.data.cah, null, '\t')


Remove unused code

Remove unused code

--- packages/rocketchat-integrations/server/triggers.coffee
+++ packages/rocketchat-integrations/server/triggers.coffee
@@ -17,8 +17,6 @@

 ExecuteTriggerUrl = (url, trigger, message, room, tries=0) ->
-	urlObj = URL.parse url
-
 	console.log tries
 	word = undefined
 	if trigger.triggerWords?.length > 0


Send request errors to callback

Send request errors to callback

--- src/installer.coffee
+++ src/installer.coffee
@@ -167,6 +167,8 @@
       headers:
         authorization: token
     readStream = request.get(requestSettings)
+    readStream.on 'error', (error) ->
+      callback("Unabled to download package (#{error.message}): #{packageUrl}")
     readStream.on 'response', (response) ->
       if response.statusCode is 200
         filePath = path.join(temp.mkdirSync(), 'package.tgz')
@@ -174,7 +176,7 @@
         readStream.pipe(writeStream)
         writeStream.on 'close', -> callback(null, filePath)
       else
-        callback("Unabled to download package URL (#{response.statusCode}): #{packageUrl}")
+        callback("Unable to download package (#{response.statusCode}): #{packageUrl}")

   # Install the package with the given name and optional version
   #


Update intel-map.coffee

Update intel-map.coffee

--- src/intel-map.coffee
+++ src/intel-map.coffee
@@ -40,6 +40,6 @@
     url = intelmapUrl coords
     msg.reply url

-  robot.respond /(intelmap|intel map|intel)(?: for)?\s(.*)/i, (msg) ->
+  robot.respond /(intelmap|intel map)(?: for)?\s(.*)/i, (msg) ->
     location = msg.match[2]
     lookupLatLong msg, location, sendIntelLink


1. Change NodesworkComponent's constructor API 2. Add class method intialize to NodesworkComponent.

--- src/nodeswork-component/index.coffee
+++ src/nodeswork-component/index.coffee
@@ -1,11 +1,22 @@
 _ = require 'underscore'

+# Base Nodeswork Component class.
+#
 exports.NodesworkComponent = class NodesworkComponent

-  constructor: ({@user, @userApplet, @applet}) ->
+  # Ctx will be passed in as parameter, deconstruct what you need within
+  # constructor.
+  constructor: (ctx) ->
+
+  # Called within nodeswork.start().
+  @initialize: () ->

 exports.Messager = class Messager extends NodesworkComponent
+
+  constructor: (ctx) ->
+    super ctx
+    {@user, @applet} = ctx

   sendMessage: (opts) ->
     if _.isString opts then opts = message: opts
@@ -17,8 +28,6 @@
     @nodeswork.request {
       method:            'POST'
       url:               url
-      headers:
-        'applet-token':  @nodeswork.config 'appletToken'
       body:
         message:         message
     }


Update app.coffee

Update app.coffee

--- services/document-updater/app.coffee
+++ services/document-updater/app.coffee
@@ -1,5 +1,5 @@
 Metrics = require "metrics-sharelatex"
-Metrics.initialize("document-updater")
+Metrics.initialize("doc-updater")

 express = require('express')
 http = require("http")


working backspace also

working backspace also

--- src/public/javascripts/client.coffee
+++ src/public/javascripts/client.coffee
@@ -22,7 +22,8 @@
     # Seems like this approach would have trouble with eventual offline editing
     console.log "Got a new event:", snapshot.val()

-  $('#input').keypress (event) ->
+  $('#input').keypress((event) ->
+    console.log event.keyCode
     k = String.fromCharCode event.which
     console.log "key pressed:", k
     if k
@@ -36,3 +37,19 @@
       @value = woot.value(string)
       event.stopPropagation()
       return false
+  ).keydown (event) ->
+    if event.keyCode == 8
+      # This is the case for backspace
+      console.log "doing backspace"
+      cursor = utils.get_cursor this
+      console.log "cursor_index=", cursor
+      woot_character = woot.generate_delete cursor - 1, string
+      if woot_character
+        # We have a visible character to delete
+        woot.integrate_delete string, woot_character
+        events_ref.push
+          woot_character: woot_character
+        sequence_number += 1
+        @value = woot.value(string)
+        event.stopPropagation()
+        return false


将构建时的取消按钮功能改为关闭

将构建时的取消按钮功能改为关闭

--- lib/project/build-project-view.coffee
+++ lib/project/build-project-view.coffee
@@ -656,6 +656,7 @@
       @parentView.nextBtn.show()
       console.log "kill timer"
       @killTimer()
+      @parentView.closeView()
       #检测是否需要取消之前的构建
   killTimer: ->
     # console.log "kill timer"


Use the standard platform check

Use the standard platform check

--- src/lib/Graph.coffee
+++ src/lib/Graph.coffee
@@ -14,6 +14,7 @@
   EventEmitter = require 'emitter'

 clone = require('./Utils').clone
+platform = require './Platform'

 # This class represents an abstract NoFlo graph containing nodes
 # connected to each other with edges.
@@ -765,7 +766,7 @@
   req.send()

 exports.loadFile = (file, success, metadata = {}) ->
-  unless typeof process isnt 'undefined' and process.execPath and process.execPath.indexOf('node') isnt -1
+  if platform.isBrowser()
     try
       # Graph exposed via Component packaging
       definition = require file


Update spec to the latest API introduced in 0.3.0

--- spec/filesize-calculator-spec.coffee
+++ spec/filesize-calculator-spec.coffee
@@ -14,52 +14,60 @@
   #TODO: Test .getSize() effectively
   describe "when calling .getSize()", ->
     it "should return the correct size in bytes", ->
-      filesizeCalculator.getSize (size, err) ->
+      filesizeCalculator.getInfo (info, err) ->
         expect(err).toEqual(null)
         expect(atom.workspace.getActivePaneItem()).toExist()
-        expect(size).toEqual(5)
+        expect(info.size).toEqual(5)

   describe "when calling .makeReadable()", ->

     describe "when size is 0", ->
       it "should return 0 bytes", ->
-        filesizeCalculator.makeReadable 0, null, (size, err) ->
+        data = {size: 0}
+        filesizeCalculator.makeReadable data, null, (info, err) ->
           expect(err).toEqual(null)
-          expect(size).toEqual("0 bytes")
+          expect(info.size).toEqual("0 bytes")

     describe "when size is 1", ->
       it "should return 1 byte", ->
-        filesizeCalculator.makeReadable 1, null, (size, err) ->
+        data = {size: 1}
+        filesizeCalculator.makeReadable data, null, (info, err) ->
           expect(err).toEqual(null)
-          expect(size).toEqual("1 byte")
+          expect(info.size).toEqual("1 byte")

     describe "when size < 1024 bytes", ->
       it "should display results in bytes when with 1024 multiple value", ->
-        filesizeCalculator.makeReadable 1023, null, (size, err) ->
+        data = {size: 1023}
+        filesizeCalculator.makeReadable data, null, (info, err) ->
           expect(err).toEqual(null)
-          expect(size).toEqual("1023 bytes")
+          expect(info.size).toEqual("1023 bytes")
       it "should display resulsts in KB with 1000 multiple value", ->
         filesizeCalculator.setMultiple(1000)
-        filesizeCalculator.makeReadable 1023, null, (size, err) ->
+        data = {size: 1023}
+        filesizeCalculator.makeReadable data, null, (info, err) ->
           expect(err).toEqual(null)
-          expect(size).toEqual("1.02 KB")
+          expect(info.size).toEqual("1.02 KB")
     describe "when size < 1048576", ->
       it "should display results in KiB with 1024 multiple value", ->
-        filesizeCalculator.makeReadable 1048000, null, (size, err) ->
+        data = {size: 1048000}
+        filesizeCalculator.makeReadable data, null, (info, err) ->
           expect(err).toEqual(null)
-          expect(size).toEqual("1023.44 KiB")
+          expect(info.size).toEqual("1023.44 KiB")
       it "should display results in KiB with 1000 multiple value", ->
         filesizeCalculator.setMultiple(1000)
-        filesizeCalculator.makeReadable 1048000, null, (size, err) ->
+        data = {size: 1048000}
+        filesizeCalculator.makeReadable data, null, (info, err) ->
           expect(err).toEqual(null)
-          expect(size).toEqual("1.05 MB")
+          expect(info.size).toEqual("1.05 MB")
     describe "when size < 1073741824", ->
       it "should display results in KiB with 1024 multiple value", ->
-        filesizeCalculator.makeReadable 1073700000, null, (size, err) ->
+        data = {size: 1073700000}
+        filesizeCalculator.makeReadable data, null, (info, err) ->
           expect(err).toEqual(null)
-          expect(size).toEqual("1023.96 MiB")
+          expect(info.size).toEqual("1023.96 MiB")
       it "should display results in KiB with 1000 multiple value", ->
         filesizeCalculator.setMultiple(1000)
-        filesizeCalculator.makeReadable 1073700000, null, (size, err) ->
+        data = {size: 1073700000}
+        filesizeCalculator.makeReadable data, null, (info, err) ->
           expect(err).toEqual(null)
-          expect(size).toEqual("1.07 GB")
+          expect(info.size).toEqual("1.07 GB")


Restore env guard in integration test

Restore env guard in integration test

--- spec/integration/startup-spec.coffee
+++ spec/integration/startup-spec.coffee
@@ -1,7 +1,7 @@
 # These tests are excluded by default. To run them from the command line:
 #
 # ATOM_INTEGRATION_TESTS_ENABLED=true apm test
-# return unless process.env.ATOM_INTEGRATION_TESTS_ENABLED
+return unless process.env.ATOM_INTEGRATION_TESTS_ENABLED
 # Integration tests require a fast machine and, for now, we cannot afford to
 # run them on Travis.
 return if process.env.CI


update

update

--- src/md-reader.coffee
+++ src/md-reader.coffee
@@ -1073,7 +1073,7 @@
     .pipe @_PRE.$flatten_image_tokens                 S
     .pipe @_PRE.$reinject_html_blocks                 S
     .pipe @_PRE.$rewrite_markdownit_tokens            S
-    .pipe D.$observe ( event ) => debug '©1', jr event
+    # .pipe D.$observe ( event ) => debug '©1', jr event
     # .pipe @_PRE.$define_brackets                      S
     .pipe @_PRE.$custom_entities                      S
     .pipe @_PRE.$spurious_ampersand                  S


Fixed bug in address-selection when `addresses` is initially empty

--- app/assets/javascripts/sprangular/directives/addressSelection.coffee
+++ app/assets/javascripts/sprangular/directives/addressSelection.coffee
@@ -9,6 +9,8 @@
     $scope.existingAddress = false

     $scope.$watch 'addresses', (addresses) ->
+      return unless addresses
+
       if addresses.length > 0
         $scope.existingAddress = true


[a11y] Fixes Usage Rights Warning for screenreader

fixes CNVS-18372

Test Plan:
   - Enable Usage Rights
   - Using a screenreader go to new files
   - Navigate to a file that has been uploaded without usage rights
     information.  It will have the warning icon.
   - The screenreader should read out the warning.

Change-Id: I5828afb5a62fd2fbbf87e6f0265fe3415c2f36a5
Reviewed-on: https://gerrit.instructure.com/48472
Tested-by: Jenkins
Reviewed-by: Dan Minkevitch <2591e5f46f28d303f9dc027d475a5c60d8dea17a@instructure.com>
QA-Review: Jahnavi Yetukuri <ed2b2672ccc2e59e56c0615df7f01b579202e014@instructure.com>
Product-Review: Clay Diffrient <9dff2e5c98626d20d2848250c411e8009465bb87@instructure.com>

--- app/coffeescripts/react_files/components/UsageRightsIndicator.coffee
+++ app/coffeescripts/react_files/components/UsageRightsIndicator.coffee
@@ -11,6 +11,8 @@

   UsageRightsIndicator = React.createClass
     displayName: 'UsageRightsIndicator'
+
+    warningMessage: I18n.t('Before publishing this file, you must specify usage rights.')

     propTypes:
       model: customPropTypes.filesystemObject.isRequired
@@ -31,9 +33,11 @@
           button {
               className: 'UsageRightsIndicator__openModal btn-link'
               onClick: @handleClick
-              title: I18n.t('Before publishing this file, you must specify usage rights.')
+              title: @warningMessage
               'data-tooltip': 'top'
             },
+              span {className: 'screenreader-only'},
+                @warningMessage
               i {className: 'UsageRightsIndicator__warning icon-warning'}
         else
           null


Convert integer dropbox id to string when searching mongo

--- services/web/app/coffee/Features/Dropbox/DropboxWebhookHandler.coffee
+++ services/web/app/coffee/Features/Dropbox/DropboxWebhookHandler.coffee
@@ -14,7 +14,7 @@

 	pollDropboxUid: (dropbox_uid, callback = (error) ->) ->
 		User.find {
-			"dropbox.access_token.uid": dropbox_uid
+			"dropbox.access_token.uid": dropbox_uid.toString()
 			"features.dropbox": true
 		}, (error, users = []) ->
 			return callback(error) if error?


fix https://github.com/sagemathinc/smc/issues/1804 -- "file_use related query bug"; really bug in socket json parsing of 0 timestamp.

--- src/smc-util/misc.coffee
+++ src/smc-util/misc.coffee
@@ -312,7 +312,7 @@
     JSON.stringify(x, socket_date_replacer)

 socket_date_parser = (key, value) ->
-    if value?[SOCKET_DATE_KEY]
+    if value?[SOCKET_DATE_KEY]?
         return new Date(value[SOCKET_DATE_KEY])
     else
         return value


still faking

still faking

--- src/streak-podium.coffee
+++ src/streak-podium.coffee
@@ -15,14 +15,5 @@
 #   Nigel Rahkola <me@nigelrahkola.com>

 module.exports = (robot) ->
-  robot.hear /longest github streaks/i, (res) ->
-    res.send "supermitch: 72"
-    res.send "Jollyra: 22"
-    res.send "joshlemer: 9"
-    res.send "ananthakumaran: 6"
-    res.send "mattbrehmer: 6"
-    res.send "jennih: 6"
-    res.send "tylerfawcett: 5"
-    res.send "Abarrowman: 5"
-    res.send "nicwaller: 3"
-    res.send "trevortuepah: 3"
+  robot.hear /github streak leaders/i, (res) ->
+    res.send "supermitch: 72\nJollyra: 22\njoshlemer: 9\nananthakumaran: 6\nmattbrehmer: 6\njennih: 6\ntylerfawcett: 5\nAbarrowman: 5\nnicwaller: 3\ntrevortuepah: 3"


redmine add 実績登録機能

redmine add 実績登録機能

--- scripts/redmine.coffee
+++ scripts/redmine.coffee
@@ -6,6 +6,7 @@
 #   hubot redmine (みんな|<nickname[,nickname]>) の実績[詳細] - 前営業日の実績を教えてもらおう！
 #   hubot redmine 実績チェック - 前営業日の実績をチェックするよ！
 #   hubot redmine <nickname> 実績チェック(する|しない) - 実績チェックの対象、または対象外にするよ！
+#   hubot redmine [yyyy-mm-dd] <#9999> 99[.99](h|時間) <活動> [コメント]- 実績を登録しよう！(日付省略時は前営業日になるよ) 例) hubot redmine #41 1.25h 製造 働いたよ!
 #
 # URLS:
 #   None.
@@ -13,14 +14,13 @@
 # Notes:
 #   None.

+URL = require('url')
+QUERY = require('querystring')
+request = require('request')
 async = require('async')
-request = require('request')
-URL = require('url')
+rssparser = require('rssparser')
+cronJob = require('cron').CronJob
 _ = require('underscore')
-url = require('url')
-QUERY = require('querystring')
-cronJob = require('cron').CronJob
-rssparser = require('rssparser')
 DateUtil = require('date-utils')

 PATH_ACTIVITY = process.env.HUBOT_REDMINE_ACTIVITY_URL
@@ -84,6 +84,36 @@
       continue
     break
   return date
+# }}}
+
+getActivityId = (activity) -> # {{{
+  return 8 if activity.match /要件定義/
+  return 9 if activity.match /製造/
+  return 10 if activity.match /管理/
+  return 11 if activity.match /結テ仕/
+  return 12 if activity.match /調査/
+  return 13 if activity.match /見積/
+  return 14 if activity.match /リリース/
+  return 15 if activity.match /ヒアリング/
+  return 16 if activity.match /開発環境/
+  return 17 if activity.match /オペマニ/
+  return 18 if activity.match /営業/
+  return 24 if activity.match /社内手続き/
+  return 25 if activity.match /現状分析/
+  return 26 if activity.match /教育/
+  return 27 if activity.match /社内環境/
+  return 28 if activity.match /業務外作業/
+  return 29 if activity.match /引継/
+  return 30 if activity.match /土曜出勤/
+  return 31 if activity.match /提案活動/
+  return 32 if activity.match /単テ仕/
+  return 33 if activity.match /単テ実/
+  return 34 if activity.match /基本設計/
+  return 35 if activity.match /詳細設計/
+  return 37 if activity.match /結テ実/
+  return 38 if activity.match /PMO/
+  return 39 if activity.match /ユーザー環境/
+  return null
 # }}}

 # tries to resolve ambiguous users by matching login or firstname# {{{
@@ -161,6 +191,9 @@
         return callback err, null if err or data is null
         timeEntries = data.time_entries
         callback null, timeEntries
+
+    create: (attributes, callback, opt) =>
+      @post "/time_entries.json", {time_entry: attributes}, opt, callback
 # }}}

   getActivity: () -> # {{{
@@ -272,26 +305,26 @@
     if method in ["POST", "PUT"]
       if typeof(body) isnt "string"
         body = JSON.stringify body
-
-      options.headers["Content-Length"] = body.length
+      options.body = body
+      # options.headers["Content-Length"] = body.length

     # console.log options.url
     request options, (err, response, data) ->
       if !err and response?.statusCode is 200
+        parsedData = data
         try
-          if opt.type is 'json'
-            callback null, JSON.parse(data), response
-          else if opt.type is 'atom'
-            rssparser.parseString data, {}, (error, feed) ->
-              # console.log feed
-              callback error, feed, response
-        catch err2
-          callback null, (data or { }), response
+          parsedData = JSON.parse(data)
+
+        if opt.type is 'json'
+          callback null, (parsedData or { }), response
+        else if opt.type is 'atom'
+          rssparser.parseString data, {}, (error, feed) ->
+            # console.log feed
+            callback error, feed, response
       else
-        console.log 'error: ' + response?.statusCode + ', url =>' + options.url
+        console.log 'code: ' + response?.statusCode + ', url =>' + options.url
         console.log err ||= response?.statusCode
-        #@send "Redmine がなんかエラーやわ"
-        callback err, null, response
+        callback err, data, response
   # /private request }}}
 # /Redmine }}}

@@ -390,15 +423,49 @@
       msg.send reply
   # }}}

-#    userName = msg.match[1].toLowerCase()
-#    user = whoIsThis robot, userName
-#
-#    redmine.Users name:user, (err, data) ->
-#      # console.log 'count:' + userName + ' ' + data.total_count
-#      unless data.total_count > 0
-#        msg.reply "\"#{userName}\" が redmine で見つけられない"
-#        return false
-#
-#      user = resolveUsers(user, data.users)[0]
-#      console.log user
-
+  robot.respond /redmine[\s]+(\d{2,4}(?:-|\/)\d{1,2}(?:-|\/)\d{1,2}[\s]+)?#(\d+)[\s]+(\d{1,2}(\.00?|\.25|\.50?|.75)?)[\s]*(?:h|時間)[\s]+([\S]+)(?:[\s]+"?([^"]+)"?)?/i, (msg) -> # {{{
+    userName = msg.message.user.name
+    user = whoIsThis robot, userName
+    return msg.send "誰？" unless user
+
+    [date, id, hours, hoursshosu, activity, userComments] = msg.match[1..6]
+    if date
+      date = new Date date
+      return msg.send "#{date} は日付じゃない" unless Date.validateDay(date.getDate(), date.getFullYear(), date.getMonth())
+    else
+      date = getPrevKadobi robot, Date.yesterday()
+    dateYMD = date.toYMD()
+    console.log dateYMD
+
+    if userComments?
+      comments = "#{msg.message.user.name}: #{userComments}"
+    else
+      comments = "Time logged by: #{msg.message.user.name}"
+    activityId = getActivityId activity
+    unless activityId
+      msg.reply "こんな\"活動\"はない > #{activity}\n"
+      msg.send " Usage: #{robot.name} redmine ##{id} #{hours}h 製造 #{activity}"
+      return
+
+    redmine.Users name:user, (err, data) =>
+      # console.log 'count:' + userName + ' ' + data.total_count
+      unless data.total_count > 0
+        msg.send "\"#{user}\" が redmine で見つけられない"
+        return false
+      redmineUser = resolveUsers(user, data.users)[0]
+
+      attributes =
+        issue_id: id
+        spent_on: dateYMD
+        hours: hours
+        comments: comments
+        user_id: redmineUser.id
+        activity_id: activityId
+
+      redmine.TimeEntry().create attributes, (error, data, response) ->
+        if response?.statusCode == 201
+          msg.reply "よく頑張りました！(#{dateYMD} で登録)"
+        else
+          msg.reply "なんか失敗"
+# }}}
+


embed box logging is less ambiguous :)

embed box logging is less ambiguous :)

--- client/app/Applications/Activity.kdapplication/views/embedbox.coffee
+++ client/app/Applications/Activity.kdapplication/views/embedbox.coffee
@@ -220,7 +220,7 @@
       log "There was unsafe content.",data,data?.safe_type,data?.safe_message
       @hide()
     else
-      log "Error!",data?.error_type,data?.error_message
+      log "EmbedBox encountered an Error!",data?.error_type,data?.error_message

   embedExistingData:(data={},options={},callback=noop)=>
     unless data.type is "error"


pass angle test

pass angle test

--- src/test/test_god.coffee
+++ src/test/test_god.coffee
@@ -22,10 +22,10 @@
     t.notOk vector.equal(a,b), "vector inequality"
     v_equal vector.add(a,b), [1,1], 'vector add'

-    t.equal vector.angle([ 1, 0]), 0, 'vector angle'
-    t.equal vector.angle([ 0, 1]), 0, 'vector angle'
-    t.equal vector.angle([-1, 0]), 0, 'vector angle'
-    t.equal vector.angle([ 0,-1]), 0, 'vector angle'
+    t.equal vector.angle([ 1, 0]),   0, 'vector angle'
+    t.equal vector.angle([ 0, 1]),  90, 'vector angle'
+    t.equal vector.angle([-1, 0]), 180, 'vector angle'
+    t.equal vector.angle([ 0,-1]), -90, 'vector angle'

     t.end()


Koding Provider: while checking usages take earned JReward amount into account

--- workers/social/lib/social/models/computeproviders/koding.coffee
+++ workers/social/lib/social/models/computeproviders/koding.coffee
@@ -3,7 +3,7 @@
 KodingError       = require '../../error'

 Regions           = require 'koding-regions'
-
+{clone}           = require 'underscore'
 {argv}            = require 'optimist'
 KONFIG            = require('koding-config-manager').load("main.#{argv.c}")

@@ -67,7 +67,23 @@
       then plan = 'free'
       else plan = subscription.planTitle

-      callback err, PLANS[plan]
+      # we need to clone the plan data since we are using global data here,
+      # when we modify it at line 84 everything will be broken after the
+      # first operation until this social restarts ~ GG
+      planData  = clone PLANS[plan]
+
+      JReward   = require '../rewards'
+      JReward.fetchEarnedAmount
+        unit     : 'MB'
+        type     : 'disk'
+        originId : client.r.account.getId()
+
+      , (err, amount)->
+
+        amount = 0  if err
+        planData.storage += Math.floor amount / 1000
+
+        callback err, planData

   checkUsage = (usage, plan, storage)->


Updated memo_key lookup to use account.options

--- dl/src/chain/lookup.coffee
+++ dl/src/chain/lookup.coffee
@@ -83,7 +83,7 @@

         promise_memo_key = new Promise (resolve)->
             _private.deferred_lookup index_name, name_key_or_id, (account)->
-                resolve(account.memo_key)
+                resolve(account.options.memo_key)

         ret = resolve: undefined
         ((ret)->


Rename @proxy to @alias

Rename @proxy to @alias

--- framer/SVGBaseLayer.coffee
+++ framer/SVGBaseLayer.coffee
@@ -13,7 +13,7 @@
 	@define "html",	get: ->	@_element.outerHTML or ""
 	@define "width", get: -> @_width
 	@define "height", get: -> @_height
-
+
 	# Disabled properties
 	@undefine ["label", "blending", "image"]
 	@undefine ["blur", "brightness", "saturate", "hueRotate", "contrast", "invert", "grayscale", "sepia"] # webkitFilter properties
@@ -25,8 +25,8 @@
 	@undefine ["borderRadius", "cornerRadius", "borderStyle"]
 	@undefine ["constraintValues", "htmlIntrinsicSize"]

-	# Proxied helpers
-	@proxy = (propertyName, proxiedName) ->
+	# Aliassed helpers
+	@alias = (propertyName, proxiedName) ->
 		@define propertyName,
 			get: ->
 				@[proxiedName]
@@ -34,9 +34,9 @@
 				return if @__applyingDefaults
 				@[proxiedName] = value

-	@proxy "borderColor", "stroke"
-	@proxy "strokeColor", "stroke"
-	@proxy "borderWidth", "strokeWidth"
+	@alias "borderColor", "stroke"
+	@alias "strokeColor", "stroke"
+	@alias "borderWidth", "strokeWidth"

 	# Overridden functions from Layer
 	_insertElement: ->


tweak glyph plot defaults

tweak glyph plot defaults

--- static/coffee/glyph_renderers.coffee
+++ static/coffee/glyph_renderers.coffee
@@ -152,9 +152,9 @@
   line_dash: ""

   font: "helvetica"
-  font_size: "12px"
+  font_size: "1em"
   font_style: "normal"
-  font_color: "gray"
+  font_color: "#444444"
   font_alpha: 1.0
   text_align: "left"
   text_baseline: "bottom"


Fix behavior of stroke picker when switching tools (fix #331)

--- src/reactGUI/StrokeWidthPicker.coffee
+++ src/reactGUI/StrokeWidthPicker.coffee
@@ -4,9 +4,12 @@

 module.exports = React.createClass
   displayName: 'StrokeWidthPicker'
-  getState: -> {strokeWidth: @props.tool.strokeWidth}
+
+  getState: (tool=@props.tool) -> {strokeWidth: tool.strokeWidth}
   getInitialState: -> @getState()
-  mixins: [createSetStateOnEventMixin('toolChange')]
+  mixins: [createSetStateOnEventMixin('setStrokeWidth')]
+
+  componentWillReceiveProps: (props) -> @setState @getState(props.tool)

   render: ->
     {ul, li, svg, circle, div} = React.DOM
@@ -24,9 +27,7 @@
           (div \
             {
               className: buttonClassName,
-              onClick: =>
-                @props.lc.trigger 'setStrokeWidth', strokeWidth
-                @setState @getState()
+              onClick: => @props.lc.trigger 'setStrokeWidth', strokeWidth
             },
             (svg \
               {


Add a debug helper to handlebars

Add a debug helper to handlebars

--- src/client/handlebars-helpers.coffee
+++ src/client/handlebars-helpers.coffee
@@ -291,8 +291,22 @@
       ret = items.join sep
       ret += options.hash['lastSep'] + last if last
       ret
-
-
+    ###*
+    * THE DEBUG HELPER
+    *
+    *      usage: {{debug}} or {{debug someValue}}
+    *
+    * @param  {Anything}  optional value to debug
+    ###
+    HandlebarsHelpers.debug = (optionalValue)->
+        console.log("Current Context")
+        console.log("====================")
+        console.log(@)
+
+        if (optionalValue)
+          console.log("Value")
+          console.log("====================")
+          console.log(optionalValue)

     handlebars.registerHelper(k, v) for k,v of HandlebarsHelpers


Session now handles configuration and injects itself into handlers

--- src/wrathnet/sessions/session.coffee
+++ src/wrathnet/sessions/session.coffee
@@ -12,8 +12,11 @@
 # Denotes a session to be used as entry for the WrathNet foundation
 class WrathNet.sessions.Session

+  # Imports
+  Config = WrathNet.entities.Config
+
   # Creates a new session for given expansion
-  constructor: (expansion, userAgent) ->
+  constructor: (expansion, config) ->

     # Ensure the expansion is an instance
     unless expansion instanceof WrathNet.expansions.Expansion
@@ -22,10 +25,10 @@
     # Holds the expansion this session uses
     @expansion = expansion

-    # Holds user-agent information
-    @userAgent = userAgent
+    # Holds configuration
+    @config = config || new Config()

     # Holds the various handlers
-    @auth = new expansion.authHandler()
-    @realms = new expansion.realmHandler()
-    @world = new expansion.worldHandler()
+    @auth = new expansion.authHandler(@)
+    @realms = new expansion.realmHandler(@)
+    @world = new expansion.worldHandler(@)


Split Game#move_players into Game#move_player

--- game.coffee
+++ game.coffee
@@ -68,17 +68,21 @@
             listener.notify(player, p, new Point(pos.pos.x, pos.pos.y)) for listener in @listeners

     move_players: (elapsed_time, new_time) ->
-      for player in @players
-        lastpos = player.currentLinePos()
-        lastpos = player.lastPos() if !lastpos or lastpos.length == 0
-        if lastpos.direction == Game.WEST
-          player.addToLine(new Position([lastpos.x - (Game.VELOCITY * elapsed_time), lastpos.y], Game.WEST, new_time))
-        else if lastpos.direction == Game.EAST
-          player.addToLine(new Position([lastpos.x + (Game.VELOCITY * elapsed_time), lastpos.y], Game.EAST, new_time))
-        else if lastpos.direction == Game.SOUTH
-          player.addToLine(new Position([lastpos.x, lastpos.y + (Game.VELOCITY * elapsed_time)], Game.SOUTH, new_time))
-        else if lastpos.direction == Game.NORTH
-          player.addToLine(new Position([lastpos.x, lastpos.y - (Game.VELOCITY * elapsed_time)], Game.NORTH, new_time))
+      @move_player(player, elapsed_time, new_time) for player in @players
+
+    move_player: (player, elapsed_time, new_time, lastpos2) ->
+      lastpos = player.currentLinePos()
+      lastpos = player.lastPos() if !lastpos or lastpos.length == 0
+      lastpos = lastpos2 if lastpos2?
+      if lastpos.direction == Game.WEST
+        player.addToLine(new Position([lastpos.x - (Game.VELOCITY * elapsed_time), lastpos.y], Game.WEST, new_time))
+      else if lastpos.direction == Game.EAST
+        player.addToLine(new Position([lastpos.x + (Game.VELOCITY * elapsed_time), lastpos.y], Game.EAST, new_time))
+      else if lastpos.direction == Game.SOUTH
+        player.addToLine(new Position([lastpos.x, lastpos.y + (Game.VELOCITY * elapsed_time)], Game.SOUTH, new_time))
+      else if lastpos.direction == Game.NORTH
+        player.addToLine(new Position([lastpos.x, lastpos.y - (Game.VELOCITY * elapsed_time)], Game.NORTH, new_time))
+

     render_game: ->
       @canvas.renderAll()


Add key bindings for the whole-word option

--- keymaps/find-and-replace.cson
+++ keymaps/find-and-replace.cson
@@ -28,6 +28,7 @@
   'cmd-alt-/': 'find-and-replace:toggle-regex-option'
   'cmd-alt-c': 'find-and-replace:toggle-case-option'
   'cmd-alt-s': 'find-and-replace:toggle-selection-option'
+  'cmd-alt-w': 'find-and-replace:toggle-whole-word-option'

 '.platform-win32 .find-and-replace, .platform-linux .find-and-replace':
   'shift-enter': 'find-and-replace:show-previous'
@@ -35,6 +36,7 @@
   'ctrl-alt-/': 'find-and-replace:toggle-regex-option'
   'ctrl-alt-c': 'find-and-replace:toggle-case-option'
   'ctrl-alt-s': 'find-and-replace:toggle-selection-option'
+  'ctrl-alt-w': 'find-and-replace:toggle-whole-word-option'

 '.platform-darwin .project-find':
   'cmd-alt-/': 'project-find:toggle-regex-option'


Saco pass

Saco pass

--- src/processor.coffee
+++ src/processor.coffee
@@ -25,7 +25,7 @@

         newrelic().startBackgroundTransaction @apm.transactionName, @apm.group, () ->
           $promise
-          .tapCatch (err) -> newrelic().noticeError new Error("#{err.type} - #{JSON.stringify(err.detail)}")
+          .tapCatch (err) -> newrelic().noticeError new Error(JSON.stringify(_.omit(err.detail, "request.auth.pass")))

       execute()
       .tap => @_emitEvent "successful", { context, id, notification }


More AMBY codes

More AMBY codes

--- assets/js/arcade.coffee
+++ assets/js/arcade.coffee
@@ -11,7 +11,7 @@
     'fox', 'gold', 'krck', '6music', 'premier',
     'rogers', 'brewers', 'penty', 'tones', 'bates',
     'watson', 'amby', 'tour', 'dancer', 'ssd',
-    'anyword'
+    'anyword', 'amyb', 'amby!'
   ]

   $(".arcade-player").click ->


Remove tab on empty line.

Remove tab on empty line.

--- src/trans/fr/room.coffee
+++ src/trans/fr/room.coffee
@@ -36,5 +36,5 @@
     activateArrow       : "Cliquez ici afin d'activer la caméra."
     userListArrow       : "Voici la liste de tous les utilisateurs présents dans le salon."
     endMessage          : "Bonne utilisation ;)"
-
+
 exports.transDef = transDef


exclude archived periods from number of sections default

--- tutor/src/flux/new-course.coffee
+++ tutor/src/flux/new-course.coffee
@@ -2,6 +2,7 @@
 _ = require 'lodash'

 {CourseListingActions} = require './course-listing'
+PeriodHelper = require '../helpers/period'

 DEFAULTS =
   course_type: 'tutor'
@@ -94,7 +95,7 @@
       offering_id: course.offering_id
       cloned_from_id: course.id
       name: course.name
-      num_sections: course.periods.length
+      num_sections: PeriodHelper.activePeriods(course).length

     @set(newCourse)


added float builtin func

added float builtin func

--- src/builtin/functions.coffee
+++ src/builtin/functions.coffee
@@ -20,3 +20,6 @@

 abs: (operand) ->
   return operand.__abs__()
+
+float: (operand) ->
+  return new Number(window.Number(operand))


Fix bug where tests from new ES code being included in requirejs wrapped code

--- services/web/test/unit_frontend/coffee/test-main.coffee
+++ services/web/test/unit_frontend/coffee/test-main.coffee
@@ -3,7 +3,7 @@
 tests = []
 for file of window.__karma__.files
   if window.__karma__.files.hasOwnProperty(file)
-    if /Tests\.js$/.test(file)
+    if /test\/unit_frontend\/js.+Tests.js$/.test(file)
       tests.push(file)

 requirejs.config


LocationRange#isCollapsed should perform a deep comparison

--- src/trix/models/location_range.coffee
+++ src/trix/models/location_range.coffee
@@ -10,7 +10,7 @@
     {@index, @offset} = @start

   isCollapsed: ->
-    @start is @end
+    @start.index is @end.index and @start.offset is @end.offset

   isInSingleIndex: ->
     @start.index is @end.index


Only install on darwin

Only install on darwin

--- src/command-installer.coffee
+++ src/command-installer.coffee
@@ -31,7 +31,7 @@
     "/usr/local/bin"

   install: (commandPath, askForPrivilege, callback) ->
-    return unless process.platform in ['darwin', 'linux']
+    return unless process.platform is 'darwin'

     commandName = path.basename(commandPath, path.extname(commandPath))
     destinationPath = path.join(@getInstallDirectory(), commandName)


Properly pass the collection to the model.

--- app/assets/javascripts/modules/timelet/views/layout/timelets.js.coffee
+++ app/assets/javascripts/modules/timelet/views/layout/timelets.js.coffee
@@ -42,7 +42,7 @@
   # First saves any changes to the current model.
   #
   createTimelet: =>
-    @model = new Essence.Models.Timelet collection: @collection
+    @model = new Essence.Models.Timelet {}, collection: @collection

     @clock.currentView.model.save()
     @clock.show new Essence.Views.Clock model: @model, parent: @


Implements Volontariat.RankingItem#thing.

--- app/assets/javascripts/voluntary_ranking/models/ranking_item.js.coffee
+++ app/assets/javascripts/voluntary_ranking/models/ranking_item.js.coffee
@@ -1,6 +1,7 @@
-VoluntaryOnEmberjs.RankingItem = DS.Model.extend
+Volontariat.RankingItem = DS.Model.extend
   ranking: DS.belongsTo('ranking')
-  #thing: DS.belongsTo('thing', { polymorphic: true })
+  thing: DS.belongsTo('thing', { polymorphic: true })
+  thingId: DS.attr('number')
   thingType: DS.attr('string')
   thingName: DS.attr('string')
   position: DS.attr('number')


spew removal

spew removal

--- lib/closure-conversion.coffee
+++ lib/closure-conversion.coffee
@@ -624,12 +624,5 @@

         passes.forEach (passType) ->
                 pass = new passType()
-
                 tree = pass.visit tree
-                console.log "after #{passType.name}"
-                console.log escodegen.generate tree
-                console.log()
-                console.log()
-                console.log()
-
         tree


Assert unmodified and modified replaced properties

--- test/unit/model/Document-spec.coffee
+++ test/unit/model/Document-spec.coffee
@@ -319,27 +319,44 @@
     # it 'should add document to recent updated documents list'

   describe '#replace()', ->
-    beforeEach -> d = JSON.parse(JSON.stringify(data.steder))
-
-    it 'should replace data for document in databse', (done) ->
-      doc.replace JSON.parse(JSON.stringify(data.steder)), (err, warn, d) ->
+    sted1 = sted2 = null
+
+    beforeEach ->
+      sted1 = JSON.parse JSON.stringify data.steder
+      sted2 = JSON.parse JSON.stringify data.steder
+      sted1._id = sted2._id = doc.id.toString()
+
+    it 'should replace data for document in database', (done) ->
+      doc.replace sted1, (err, warn) ->
         assert.ifError err
         assert.deepEqual warn, []

-        doc.getFull {}, (err, d) ->
-          assert.ifError err
-
-          ignore = ['_id', 'checksum', 'endret']
-          assert.deepEqual d[key], val for key, val of data.steder when key not in ignore
+        doc.getFull {}, (err, doc) ->
+          assert.ifError err
+
+          # This is auto converted to string over HTTP
+          doc._id = doc._id.toString()
+
+          ignore = ['checksum', 'endret']
+          for key, val of sted2 when key not in ignore
+            assert.deepEqual doc[key], val
+
+          for key, val of sted2 when key in ignore
+            assert.notEqual doc[key], val
+
           done()

     it 'should replace data for document in cache', (done) ->
-      doc.replace JSON.parse(JSON.stringify(data.steder)), (err, warn, d) ->
+      doc.replace sted1, (err, warn) ->
         assert.ifError err
         assert.deepEqual warn, []

-        ignore = ['_id', 'checksum', 'endret']
-        assert.deepEqual val, data.steder[key] for key, val of doc.get() when key not in ignore
+        ignore = ['checksum', 'endret']
+        for key, val of doc.get() when key not in ignore
+          assert.deepEqual val, sted2[key]
+
+        for key, val of doc.get() when key in ignore
+          assert.notEqual val, sted2[key]

         done()


Tidy up.

Tidy up.

--- c3/app/assets/javascripts/s4_careYouProvide/g7_moreAboutTheCare.coffee
+++ c3/app/assets/javascripts/s4_careYouProvide/g7_moreAboutTheCare.coffee
@@ -1,9 +1,9 @@
 window.initEvents = (spent35HoursCaringBeforeClaimY, spent35HoursCaringBeforeClaimN, day, month, year) ->
   $("#" + spent35HoursCaringBeforeClaimY).on "click", ->
-    $("#careStartDateWrap").slideDown 500
+    $("#careStartDateWrap").slideDown()

   $("#" + spent35HoursCaringBeforeClaimN).on "click", ->
-    $("#careStartDateWrap").slideUp 500, ->
+    $("#careStartDateWrap").slideUp() ->
       $("#" + day).val("")
       $("#" + month).val("")
       $("#" + year).val("")


Fix crash in user registration

Fix crash in user registration

--- hamwerk/server/main.coffee
+++ hamwerk/server/main.coffee
@@ -94,5 +94,7 @@
 Accounts.onCreateUser (options, user) ->
     if options.profile?
         user.profile = options.profile
+    else
+        user.profile = {}
     user.profile.onboarded = no
     return user


added test

added test

--- test/crystal/validate/exports.coffee
+++ test/crystal/validate/exports.coffee
@@ -69,6 +69,18 @@
         (-> new crystal config)
           .should.not.throw "`exports.ConfigGenerator.example` is required."

+      it 'should not require a filename', () ->
+        # object
+        config = {
+          exports: {
+            ConfigGenerator: {
+              type: 'generator'
+            }
+          }
+        }
+        (-> new crystal config)
+          .should.not.throw "`exports.ConfigGenerator.filename` is required."
+
       it 'should not require a helper', () ->
         # object
         config = {


add Element.prototype.getElements? tests

--- test/integration/element.test.coffee
+++ test/integration/element.test.coffee
@@ -188,3 +188,32 @@
         @browser.waitForElementNotVisible('.does-not-exist', 10)
       assert.equal 'Timeout (10ms) waiting for element (.does-not-exist) to not be visible.', error.message

+  describe '#getElement', ->
+    before ->
+      @element = @browser.getElement 'body'
+
+    it 'fails if selector is undefined', ->
+      assert.throws ->
+        @element.getElement(undefined)
+
+    it 'fails if selector is not a String', ->
+      assert.throws ->
+        @element.getElement(->)
+
+    it 'succeeds if selector is a String', ->
+      @element.getElement('.message')
+
+  describe '#getElements', ->
+    before ->
+      @element = @browser.getElement 'body'
+
+    it 'fails if selector is undefined', ->
+      assert.throws ->
+        @element.getElements(undefined)
+
+    it 'fails if selector is not a String', ->
+      assert.throws ->
+        @element.getElements(->)
+
+    it 'succeeds if selector is a String', ->
+      @element.getElements('.message')


Lower logs timeouts to pass on travis

Lower logs timeouts to pass on travis

--- test/integration/logsAPITests.coffee
+++ test/integration/logsAPITests.coffee
@@ -43,15 +43,15 @@
                           server.start apiPort: 8080, ->
                             done()
                             # We need to go deeper!
-                      , 50 * global.testTimeoutFactor
-                    , 50 * global.testTimeoutFactor
-                  , 50 * global.testTimeoutFactor
-                , 50 * global.testTimeoutFactor
-              , 50 * global.testTimeoutFactor
-            , 50 * global.testTimeoutFactor
-          , 50 * global.testTimeoutFactor
-        , 50 * global.testTimeoutFactor
-      , 50 * global.testTimeoutFactor
+                      , 15 * global.testTimeoutFactor
+                    , 15 * global.testTimeoutFactor
+                  , 15 * global.testTimeoutFactor
+                , 15 * global.testTimeoutFactor
+              , 15 * global.testTimeoutFactor
+            , 15 * global.testTimeoutFactor
+          , 15 * global.testTimeoutFactor
+        , 15 * global.testTimeoutFactor
+      , 15 * global.testTimeoutFactor

     after (done) ->
       logger.transports.MongoDB.level = 'debug'


PUBDEV-1404 Add proxy methods to fetch frame slices by column/s.

--- src/ext/modules/proxy.coffee
+++ src/ext/modules/proxy.coffee
@@ -153,8 +153,16 @@
   requestFrame = (key, go) ->
     doGet "/3/Frames/#{encodeURIComponent key}", unwrap go, (result) -> head result.frames

+  requestFrameSlice = (key, searchTerm, offset, count, go) ->
+    #TODO send search term
+    doGet "/3/Frames/#{encodeURIComponent key}?column_offset=#{offset}&column_count=#{count}", unwrap go, (result) -> head result.frames
+
   requestFrameSummary = (key, go) ->
     doGet "/3/Frames/#{encodeURIComponent key}/summary", unwrap go, (result) -> head result.frames
+
+  requestFrameSummarySlice = (key, searchTerm, offset, count, go) ->
+    #TODO send search term
+    doGet "/3/Frames/#{encodeURIComponent key}/summary?column_offset=#{offset}&column_count=#{count}", unwrap go, (result) -> head result.frames

   requestFrameSummaryWithoutData = (key, go) ->
     doGet "/3/Frames/#{encodeURIComponent key}/summary?_exclude_fields=frames/chunk_summary,frames/distribution_summary,frames/vec_ids,frames/columns/data,frames/columns/domain,frames/columns/histogram_bins,frames/columns/percentiles", (error, result) ->
@@ -451,7 +459,9 @@
   link _.requestSplitFrame, requestSplitFrame
   link _.requestFrames, requestFrames
   link _.requestFrame, requestFrame
+  link _.requestFrameSlice, requestFrameSlice
   link _.requestFrameSummary, requestFrameSummary
+  link _.requestFrameSummarySlice, requestFrameSummarySlice
   link _.requestFrameSummaryWithoutData, requestFrameSummaryWithoutData
   link _.requestDeleteFrame, requestDeleteFrame
   link _.requestRDDs, requestRDDs


Fix MIDB

Fix MIDB

--- src/formula_evaluator.coffee
+++ src/formula_evaluator.coffee
@@ -560,20 +560,19 @@
     str = @expectString(args[0])
     start = @expectInteger(args[1])
     len = @expectInteger(args[2])
+    --start
     if start < 0 || len < 0
       @error(ERROR_VALUE)
-    prefix = mbsubstr(str, 0, start)
     prefix_space = ""
     #2バイト文字の途中から始まっていた場合
-    if prefix.length < start
+    if multibyteLength(mbsubstr(str, 0, start)) < start
       prefix_space = " "
-    len2 = start + len - 1
-    str = mbsubstr(str, 0, len2)
+    len2 = start + len
     suffix_space = ""
     #2バイト文字の途中で終わっていた場合
-    if str.length < len2
+    if multibyteLength(mbsubstr(str, 0, len2)) < len2
       suffix_space = " "
-    prefix_space + str.replace(prefix, "") + suffix_space
+    prefix_space + mbsubstr(str, start, len) + suffix_space

   MINUTE: (args)->
     @checkArgumentSize(args, 1)


Support RIGHT and RIGHTB

Support RIGHT and RIGHTB

--- src/formula_evaluator.coffee
+++ src/formula_evaluator.coffee
@@ -771,6 +771,19 @@
       @error(ERROR_VALUE)
     str.repeat(n)

+  RIGHT: (args)->
+    @checkArgumentSize(args, 1, 2)
+    str = @expectString(args[0])
+    args[0] = str.split("").reverse().join("")
+    @LEFT(args).split("").reverse().join("")
+
+  RIGHTB: (args)->
+    @checkArgumentSize(args, 1, 2)
+    str = @expectString(args[0])
+    args[0] = str.split("").reverse().join("")
+    @LEFTB(args).split("").reverse().join("")
+
+
   ROMAN: (args)->
     @checkArgumentSize(args, 1, 2)
     n = @expectInteger(args[0])


Tests:Account:Accountpage: Added last name test.

--- tests/src/account/accountpage.coffee
+++ tests/src/account/accountpage.coffee
@@ -32,3 +32,13 @@
     helpers.changeName(browser, inputSelector, yes)
     browser.end()

+
+  editLastName: (browser) ->
+
+    helpers.beginTest(browser)
+    paragraph = helpers.getFakeText()
+    inputSelector = '.lastname input.text'
+
+    helpers.changeName(browser, inputSelector, no)
+    browser.end()
+


#391 corrects bug when pushing multiple elements

--- src/ui/views/menu.coffee
+++ src/ui/views/menu.coffee
@@ -35,7 +35,7 @@
 templateYakYak = (viewstate, platform) ->
     tmpl = []
     if platform == 'darwin'
-        tmpl.concat [
+        tmpl.push.apply tmpl,  [
             { label: 'About YakYak', selector: 'orderFrontStandardAboutPanel:' }
             #{ type: 'separator' }
             # { label: 'Preferences...', accelerator: 'Command+,',
@@ -50,7 +50,7 @@
     }

     if platform == 'darwin'
-        tmpl.push [{
+        tmpl.push.apply tmpl, [{
                 label: 'Hide Others'
                 accelerator: acceleratorMap['hideother'][platform]
                 selector: 'hideOtherApplications:' if platform == 'darwin'
@@ -60,7 +60,7 @@
                 selector: 'unhideAllApplications:' if platform == 'darwin'
             }
         ]
-    tmpl.push [
+    tmpl.push.apply tmpl, [
         { type: 'separator' }
         {
             label: 'Open Inspector'
@@ -97,7 +97,7 @@
         }
     ]
     if platform == 'darwin'
-        tmpl.push [
+        tmpl.push.apply tmpl, [
           { type: 'separator' }
           {
             label: 'Cut'


FindTeam: use hasOneTeam instead of hasMultipleTeams to avoid breaking current email template

--- servers/lib/server/handlers/findteam.coffee
+++ servers/lib/server/handlers/findteam.coffee
@@ -50,10 +50,10 @@
         to      : email
         subject : Tracker.types.REQUESTED_TEAM_LIST
       }, {
-        teams            : groups.map (group) -> helper.createTeamItem group
-        hasMultipleTeams : groups.length > 1
-        findTeamUrl      : "#{protocol}//#{hostname}/Teams/FindTeam"
-        createTeamUrl    : "#{protocol}//#{hostname}/Teams/Create"
+        teams         : groups.map (group) -> helper.createTeamItem group
+        hasOneTeam    : groups.length is 1
+        findTeamUrl   : "#{protocol}//#{hostname}/Teams/FindTeam"
+        createTeamUrl : "#{protocol}//#{hostname}/Teams/Create"
       }, next
   ]


Remove unneeded id

Remove unneeded id

--- src/packages/fuzzy-finder/spec/fuzzy-finder-spec.coffee
+++ src/packages/fuzzy-finder/spec/fuzzy-finder-spec.coffee
@@ -209,7 +209,7 @@
           rootView.attachToDom()
           rootView.getActiveEditor().destroyActiveEditSession()

-          inputView = $$ -> @input id: "div-view"
+          inputView = $$ -> @input()
           rootView.append(inputView)
           inputView.focus()


fixing some sortability

fixing some sortability

--- javascripts/jquery.gridly.coffee
+++ javascripts/jquery.gridly.coffee
@@ -40,7 +40,6 @@
     @$el = $el
     @settings = $.extend {}, Gridly.settings, settings
     @ordinalize(@$('> *'))
-    @draggable() if @settings.draggable

   ordinalize: ($elements) =>
     for i in [0 .. $elements.length]
@@ -64,6 +63,9 @@
       start: @start
       stop: @stop

+  stationary: =>
+    @$('> *').draggable('destroy')
+
   start: (event, ui) =>
     $dragging = $(event.target)
     @ordinalize(@$sorted())
@@ -77,10 +79,18 @@

   $sorted: ($elements) =>
     ($elements || @$('> *')).sort (a,b) ->
-      aVal = parseInt($(a).data('position'))
-      bVal = parseInt($(b).data('position'))
-      return -1 if aVal < bVal
-      return +1 if aVal > bVal
+      $a = $(a)
+      $b = $(b)
+      aPosition = $a.data('position')
+      bPosition = $b.data('position')
+      aPositionInt = parseInt(aPosition)
+      bPositionInt = parseInt(bPosition)
+      return -1 if aPosition? and not bPosition?
+      return +1 if bPosition? and not aPosition?
+      return -1 if not aPosition and not bPosition and $a.index() < $b.index()
+      return +1 if not bPosition and not aPosition and $b.index() < $a.index()
+      return -1 if aPositionInt < bPositionInt
+      return +1 if bPositionInt < aPositionInt
       return 0

   drag: (event, ui) =>


scroll to bottom when toggle

scroll to bottom when toggle

--- lib/utils/console-view.coffee
+++ lib/utils/console-view.coffee
@@ -18,3 +18,4 @@

   toggleLogger: ->
     @out.toggle()
+    @out[0].scrollTop = @out[0].scrollHeight


Minor change in the message

Minor change in the message

--- library/gearing/kernel.coffee
+++ library/gearing/kernel.coffee
@@ -71,7 +71,7 @@
         port = nconf.get("server:port")
         hostname = nconf.get("server:hostname")
         message = "Booted up the kernel instance".red
-        running = "Running server at %s:%s".magenta
+        running = "Running HTTP server at %s:%s".magenta
         logger.info(running, hostname, port)
         @server.listen(port, hostname)
         logger.info(message); this


unessential code changes in the plumbs.platform

--- library/nucleus/plumbs.coffee
+++ library/nucleus/plumbs.coffee
@@ -83,23 +83,23 @@
 # object. If the user agent string is absent from the requesting
 # entity then the platform will not be defined on request object.
 # Refer to the implementation source code for more information.
-module.exports.platform = (kernel) ->
-    (request, response, next) ->
-        intern = "could not parse the platform data"
-        noParser = "platform parse library malfunction"
-        noPlatform = "missing platform middleware lib"
-        noHeaders = "unable to locate request headers"
-        assert _.isObject(platform or null), noPlatform
-        assert _.isObject(request?.headers), noHeaders
-        assert _.isFunction(platform?.parse), noParser
-        agent = request.headers["user-agent"] or null
-        return next() if not agent or _.isEmpty agent
-        assert unix = moment().unix().toString().bold
-        message = "Running platform middleware at U=%s"
-        logger.debug message.toString(), unix.toString()
-        request.platform = try (platform.parse agent)
-        assert _.isObject(request.platform), intern
-        return next() unless request.headersSent
+module.exports.platform = (kernel) -> (request, response) ->
+    intern = "could not parse the platform data"
+    noParser = "platform parse library malfunction"
+    noPlatform = "missing platform middleware lib"
+    noHeaders = "unable to locate request headers"
+    assert _.isObject(platform or null), noPlatform
+    assert _.isObject(request?.headers), noHeaders
+    assert _.isFunction(platform?.parse), noParser
+    agent = request.headers["user-agent"] or null
+    return next() if not agent or _.isEmpty agent
+    assert unix = moment().unix().toString().bold
+    message = "Running platform middleware at U=%s"
+    logger.debug message.toString(), unix.toString()
+    request.platform = try (platform.parse agent)
+    assert _.isObject(request.platform), intern
+    assert _.isFunction next = _.last arguments
+    return next() unless request.headersSent

 # This middleware uses an external library to parse the incoming
 # request metadata and then coerce it by using the standards to


fixed api

fixed api

--- api.coffee
+++ api.coffee
@@ -309,10 +309,13 @@
       return
     validateApiKey req, res, (user) ->
       try
-        data = getEtherpadAPI "setText", "challenge#{req.params.challenge}", {text: req.body}, (data, err) ->
-          if not err
-            res.json {success: true}
-          else
-            res.json err
-      catch e
-        res.send 400
+        if req.body.text and typeof(req.body.text) is "string"
+          data = getEtherpadAPI "setText", "challenge#{req.params.challenge}", {text: req.body.text}, (data, err) ->
+            if not err
+              res.json {success: true}
+            else
+              res.json err
+        else
+          res.send 400
+      catch e
+        res.send 400


Require DropboxUploader.

Require DropboxUploader.

--- app.coffee
+++ app.coffee
@@ -9,6 +9,7 @@
 EmailParser = require "./services/EmailParser"
 IncomingMessage = require "./models/IncomingMessage"
 StravaUploader = require "./services/StravaUploader"
+DropboxUploader = require "./services/DropboxUploader"

 # Load environment vars from .env file.
 dotenv = require 'dotenv'


removed unnecessary comment

removed unnecessary comment

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

-# Split sentences
-# var sentences = str.replace(/\.\s+/g,'.|').replace(/\?\s/g,'?|').replace(/\!\s/g,'!|').split("|");
 # Routes
 app.get '/', routes.index


removed extra check

removed extra check

--- app.coffee
+++ app.coffee
@@ -85,7 +85,7 @@
     $('.remove').on 'click', @removeRepository

   promptAddRepo: ->
-    regexExpression = "^" + @githubHost + '|https://github.com' + "\\/([\\w-\\.]+\\/[\\w-\\.]+)"
+    regexExpression = "^" + @githubHost + "\\/([\\w-\\.]+\\/[\\w-\\.]+)"
     regex = new RegExp regexExpression
     if match = @url.match(regex)
       @currentRepo = match[1]


fixing regex to remove comments, plus refactored it and added comments

--- fizzylogo_parser.coffee
+++ fizzylogo_parser.coffee
@@ -25,6 +25,30 @@
   # collapse all multiple spaces to one
   command = command.replace /[ ]+/g, " "
   command = command.trim()
+
+removeComments = (code) ->
+  # this fizzylogo is empty-line sensitive, this regex is custom
+  # and also properly removes the lines where there are comments
+  # (i.e. it doesn't just strip the comment leaving empty the line
+  # where it was)
+  #
+  # 1st part: multi-line comments starting with new line
+  # 2nd part: multi-line comments NOT starting with new line
+  # 3rd part: single-line comments starting with new line
+  # 4th part: single-line comments NOT starting with new line
+  # 5th part: multi-line comment at the end of the text
+  # 6th part: single-line comment at the end of the text
+  #
+  # see: https://regex101.com/r/U5pYX4/1
+
+  return code.replace ///
+    (^)\/\*[\s\S]*?\*\/\n?|
+    ([^\\\n])\/\*[\s\S]*?\*\/|
+    (^)\/\/.*$\n|
+    ([^\\\n])\/\/.*$|
+    \n\/\/.*$|
+    \n\/\*[\s\S]*?\*\
+    ///gm, "$1$2$3$4"

 # replace all the strings with
 # references to a table, which we'll replace back
@@ -95,8 +119,7 @@
   console.log "codeWithoutStrings: " + command
   console.log "stringsTable: " + stringsTable

-  command = command.replace /\/\*[\s\S]*?\*\/\n?|([^\\:\n])\/\/.*$|(^)\/\/.*$\n/gm, "$1"
-
+  command = removeComments command

   command = linearize command
   console.log "linearized command: " + command


Add specs for integer order_id & product_id

--- spec/actions_manager_spec.coffee
+++ spec/actions_manager_spec.coffee
@@ -426,6 +426,19 @@
             payload = @sendbeacon_spy.args[0][1]
             expect(payload.actions[0].data).to.eql VALID_ORDER_DATA_STRINGIFIED

+        context 'when order_id is integer', ->
+          beforeEach ->
+            @data = JSON.stringify({
+              order_id: 123456,
+              revenue:  '1315.25',
+              shipping: '5.45',
+              tax:      '301.25'
+            })
+            @run()
+
+          it 'reports the action', ->
+            expect(@sendbeacon_spy).to.be.called
+
         describe 'validations', ->
           context 'when an invalid JSON object is given', ->
             beforeEach ->
@@ -514,6 +527,34 @@
           it 'stringifies the data before reporting', ->
             payload = @sendbeacon_spy.args[0][1]
             expect(payload.actions[0].data).to.eql VALID_LINE_ITEM_DATA_1_STRINGIFIED
+
+        context 'when order_id is integer', ->
+          beforeEach ->
+            @data = JSON.stringify({
+              order_id:   123456,
+              product_id: '47299441',
+              name:       'Product 1 Lalastore'
+              price:      '654.90',
+              quantity:   '1'
+            })
+            @run()
+
+          it 'reports the action', ->
+            expect(@sendbeacon_spy).to.be.called
+
+        context 'when product_id is integer', ->
+          beforeEach ->
+            @data = JSON.stringify({
+              order_id:   '123456',
+              product_id: 47299441,
+              name:       'Product 1 Lalastore'
+              price:      '654.90',
+              quantity:   '1'
+            })
+            @run()
+
+          it 'reports the action', ->
+            expect(@sendbeacon_spy).to.be.called

         describe 'validations', ->
           context 'when an invalid JSON object is given', ->