Move helper methods below specs.

Move helper methods below specs.

--- src/spec/integration-order.spec.coffee
+++ src/spec/integration-order.spec.coffee
@@ -7,6 +7,81 @@

 # Increase timeout
 jasmine.getEnv().defaultTimeoutInterval = 10000
+
+
+describe "OrderSync integration test", ->
+
+  beforeEach (done) ->
+    @sync = new OrderSync config: Config.staging
+
+    createResourcePromise(@sync._rest, '/product-types', productTypeMock())
+      .then (productType) =>
+        @productType = productType
+        createResourcePromise(@sync._rest, '/products', productMock(productType))
+    .then (product) =>
+      @product = product
+      createResourcePromise(@sync._rest, '/orders/import', orderMock(product))
+    .then (order) =>
+      @order = order
+      done()
+
+  afterEach (done) ->
+
+    # TODO: delete order (not supported by API yet)
+    deleteResourcePromise(@sync._rest, "/products/#{@product.id}?version=#{@product.version}")
+      .then (product) =>
+        deleteResourcePromise(@sync._rest, "/product-types/#{@productType.id}?version=#{@productType.version}")
+    .then (productType) =>
+      done()
+
+  it "should do nothing", (done) ->
+    expect(true).toBe true
+    done()
+
+
+xdescribe "Integration test", ->
+  ORDER_ID = "455e92b4-2a75-4cb8-bfdd-eb78c7f227cb"
+  VERSION_ID = 1
+
+  beforeEach ->
+    @sync = new OrderSync config: Config.staging
+
+  afterEach ->
+    @sync = null
+
+  it "should update an order", (done)->
+    timestamp = new Date().getTime()
+    NEW_ORDER =
+      orderState: "Complete"
+      paymentState: "Paid"
+      shipmentState: "Ready"
+    # let's reset the statues first, so that the sync will work
+    payload = JSON.stringify
+      actions: [
+        { action: "changeOrderState", orderState: "Open" }
+        { action: "changePaymentState", paymentState: "Pending" }
+        { action: "changeShipmentState", shipmentState: "Pending" }
+      ]
+      version: VERSION_ID
+    @sync._rest.POST "/orders/#{ORDER_ID}", payload, (error, response, body)=>
+      if response.statusCode is 200
+        old_order = JSON.parse(body)
+        @sync.buildActions(NEW_ORDER, old_order).update (e, r, b)->
+          expect(r.statusCode).toBe 200
+          console.error b unless r.statusCode is 200
+          updated = JSON.parse(b)
+          expect(updated).toBeDefined()
+          expect(updated.orderState).toBe "Complete"
+          expect(updated.paymentState).toBe "Paid"
+          expect(updated.shipmentState).toBe "Ready"
+          done()
+      else
+        console.error body
+        throw new Error "Could not update order"
+
+###
+helper methods
+###

 productTypeMock = () ->
   unique = new Date().getTime()
@@ -77,88 +152,4 @@
     else
       console.log body
       deferred.reject new Error(body)
-  deferred.promise
-
-describe "OrderSync integration test", ->
-
-  beforeEach (done) ->
-    @sync = new OrderSync config: Config.staging
-
-    createResourcePromise(@sync._rest, '/product-types', productTypeMock())
-      .then (productType) =>
-        @productType = productType
-        createResourcePromise(@sync._rest, '/products', productMock(productType))
-    .then (product) =>
-      @product = product
-      createResourcePromise(@sync._rest, '/orders/import', orderMock(product))
-    .then (order) =>
-      @order = order
-      done()
-
-  afterEach (done) ->
-
-    # TODO: delete order (not supported by API yet)
-    deleteResourcePromise(@sync._rest, "/products/#{@product.id}?version=#{@product.version}")
-      .then (product) =>
-        deleteResourcePromise(@sync._rest, "/product-types/#{@productType.id}?version=#{@productType.version}")
-    .then (productType) =>
-      done()
-
-  it "should do nothing", (done) ->
-    expect(true).toBe true
-    done()
-
-
-xdescribe "Integration test", ->
-  ORDER_ID = "455e92b4-2a75-4cb8-bfdd-eb78c7f227cb"
-  VERSION_ID = 1
-
-  beforeEach ->
-    @sync = new OrderSync config: Config.staging
-
-  afterEach ->
-    @sync = null
-
-  it "should get orders", (done)->
-    @sync._rest.GET "/orders/#{ORDER_ID}", (error, response, body)->
-      expect(response.statusCode).toBe 200
-      json = JSON.parse(body)
-      expect(json).toBeDefined()
-      expect(json.id).toBe ORDER_ID
-      VERSION_ID = json.version # we save the version for the udpate later (dirty, I know!)
-      done()
-
-  it "should return 404 if order is not found", (done)->
-    @sync._rest.GET "/orders/123", (error, response, body)->
-      expect(response.statusCode).toBe 404
-      done()
-
-  it "should update an order", (done)->
-    timestamp = new Date().getTime()
-    NEW_ORDER =
-      orderState: "Complete"
-      paymentState: "Paid"
-      shipmentState: "Ready"
-    # let's reset the statues first, so that the sync will work
-    payload = JSON.stringify
-      actions: [
-        { action: "changeOrderState", orderState: "Open" }
-        { action: "changePaymentState", paymentState: "Pending" }
-        { action: "changeShipmentState", shipmentState: "Pending" }
-      ]
-      version: VERSION_ID
-    @sync._rest.POST "/orders/#{ORDER_ID}", payload, (error, response, body)=>
-      if response.statusCode is 200
-        old_order = JSON.parse(body)
-        @sync.buildActions(NEW_ORDER, old_order).update (e, r, b)->
-          expect(r.statusCode).toBe 200
-          console.error b unless r.statusCode is 200
-          updated = JSON.parse(b)
-          expect(updated).toBeDefined()
-          expect(updated.orderState).toBe "Complete"
-          expect(updated.paymentState).toBe "Paid"
-          expect(updated.shipmentState).toBe "Ready"
-          done()
-      else
-        console.error body
-        throw new Error "Could not update order"
+  deferred.promise


allow string items

allow string items


LOOKS: Make discuss, demonstrate and complete icons consistent

--- src/app/common/services/task-service.coffee
+++ src/app/common/services/task-service.coffee
@@ -105,9 +105,9 @@
     fix_and_include:    'fa fa-stop'
     fix_and_resubmit:   'fa fa-wrench'
     discuss:            'fa fa-check'
-    demonstrate:        'fa fa-laptop'
-    complete:           'fa fa-check-square-o'
-    fail:               'fa fa-times-circle-o'
+    demonstrate:        'fa fa-check'
+    complete:           'fa fa-check'
+    fail:               'fa fa-times'

   taskService.statusColors =
     # Please make sure this matches task-status-colors.less


collection manager should extend luca.events

--- src/managers/collection_manager.coffee
+++ src/managers/collection_manager.coffee
@@ -43,6 +43,7 @@
   constructor: (@options={})->
     _.extend @, @options
     _.extend @, Backbone.Events
+    _.extend @, Luca.Events

     # if you are going to use more than one collection
     # manager, then you will have to specify which


took out delay before calling moveEditorButtons in playerCodeEditor as it is no longer necessary

--- web/scripts/playerCodeEditor.coffee
+++ web/scripts/playerCodeEditor.coffee
@@ -191,8 +191,7 @@
         if @parameterPopUp == undefined
             @parameterPopUp = jQuery('#parameter-pop-up')

-        if not @movingButtons
-            setTimeout @moveEditorButtons, 20
+        @moveEditorButtons()

         @parameterPopUp.hide()
         return


Use the kodingAppsController's approveApp method before marking the app as approved

--- client/landing/app/Applications/Apps.kdapplication/Views/appview.coffee
+++ client/landing/app/Applications/Apps.kdapplication/Views/appview.coffee
@@ -31,6 +31,8 @@
             callback? err
       ]
     , app
+
+    appsController = @getSingleton("kodingAppsController")

     if KD.checkFlag 'super-admin'
       @approveButton = new KDToggleButton
@@ -39,8 +41,13 @@
         defaultState    : if app.approved then "Disapprove" else "Approve"
         states          : [
           "Approve", (callback)->
-            app.approve yes, (err)->
-              callback? err
+            appsController.approveApp app, (err)=>
+              if not err
+                app.approve yes, (err)->
+                  if err then warn err
+                  callback? err
+              else
+                callback? err
           "Disapprove", (callback)->
             app.approve no, (err)->
               callback? err
@@ -55,7 +62,6 @@
       else
         @likeButton.setState "Like"

-    appsController = @getSingleton("kodingAppsController")

     if app.versions?.length > 1
       menu =


Improve copy portable error message

Improve copy portable error message

--- src/browser/main.coffee
+++ src/browser/main.coffee
@@ -66,7 +66,7 @@
     try
       AtomPortable.setPortable(atomHome)
     catch error
-      console.log("Failed converting portable directory: #{error.message} #{error.stack}")
+      console.log("Failed copying portable directory: #{atomHome} to #{AtomPortable.getPortableAtomHomePath()}. #{error.message} #{error.stack}")

   if AtomPortable.isPortableInstall(process.platform, process.env.ATOM_HOME, atomHome)
     atomHome = AtomPortable.getPortableAtomHomePath()


Like/Unlike works like a charm

Like/Unlike works like a charm

--- client/landing/app/Applications/Apps.kdapplication/Views/appview.coffee
+++ client/landing/app/Applications/Apps.kdapplication/Views/appview.coffee
@@ -21,15 +21,21 @@

     @likeButton = new KDToggleButton
       style           : "kdwhitebtn"
-      dataPath        : "followee"
       states          : [
         "Like", (callback)->
           app.like (err)->
             callback? err
         "Unlike", (callback)->
-          callback? null
-      ]
-    , app
+          app.like (err)->
+            callback? err
+      ]
+    , app
+
+    app.checkIfLikedBefore (err, likedBefore)=>
+      if likedBefore
+        @likeButton.setState "Unlike"
+      else
+        @likeButton.setState "Like"

     appsController = @getSingleton("kodingAppsController")

@@ -85,7 +91,7 @@

Version {{ #(manifest.version) or "---" }}
Updated: ---

{{#(counts.installed) or 0}}
INSTALLS

-
{{#(counts.likes) or 0}}
Likes

+
{{#(meta.likes) or 0}}
Likes

{{#(counts.followers) or 0}}
Followers


Fixed the issue caused by not subscribing to the files collection on the client.

--- collections/files.coffee
+++ collections/files.coffee
@@ -23,6 +23,8 @@

     if Meteor.isServer
       Meteor.publish 'files', -> Files.find()
+    else
+      Meteor.subscribe('files')

     # File IDs to deferred promises containing their data.
     fileCache = {}


fix upload permissions introduced in raik:ufs 0.3.4

--- lib/fileUpload.coffee
+++ lib/fileUpload.coffee
@@ -74,13 +74,11 @@
 					token = cookie.get('rc_token', rawCookies) if rawCookies?

 					unless uid and token and RocketChat.models.Users.findOneByIdAndLoginToken(uid, token)
-						res.statusCode = 403
-						res.end('Not Allowed')
-						# Just to abort the request
-						# See https://github.com/jalik/jalik-ufs/issues/28
-						throw new Meteor.Error 403, 'Not Allowed'
+						res.writeHead 403
+						return false

 				res.setHeader 'content-disposition', "attachment; filename=\"#{ encodeURIComponent(file.name) }\""
+				return true

 Meteor.startup ->
 	if Meteor.isServer


more optimizations

more optimizations

--- lib/fingerings.coffee
+++ lib/fingerings.coffee
@@ -101,19 +101,21 @@
     strings[position.string].push position for position in positions
     strings

-  collectFingeringPositions = (string_frets) ->
-    return [[]] unless string_frets.length
-    frets = string_frets[0]
-    followingFingerPositions = collectFingeringPositions(string_frets[1..])
+  collectFingeringPositions = (stringFrets) ->
+    return [[]] unless stringFrets.length
+    frets = stringFrets[0]
+    followingFingerPositions = collectFingeringPositions(stringFrets[1..])
     return followingFingerPositions.concat(([n].concat(right) \
       for n in frets for right in followingFingerPositions)...)

   generateFingerings = ->
-    _.flatten(new Fingering {positions, chord, barres, instrument} \
-      for barres in collectBarreSets(instrument, positions) \
-      for positions in collectFingeringPositions(fretsPerString))
-
-  chord_note_count = chord.pitchClasses.length
+    fingerings = []
+    for barres in collectBarreSets(instrument, positions)
+      for positions in collectFingeringPositions(fretsPerString)
+        fingerings.push new Fingering {positions, chord, barres, instrument}
+    fingerings
+
+  chordNoteCount = chord.pitchClasses.length

   #
@@ -121,10 +123,14 @@
   #

   countDistinctNotes = (fingering) ->
-    _.chain(fingering.positions).pluck('intervalClass').uniq().value().length
+    # _.chain(fingering.positions).pluck('intervalClass').uniq().value().length
+    pitches = []
+    for {intervalClass} in fingering.positions
+      pitches.push intervalClass unless intervalClass in pitches
+    return pitches.length

   hasAllNotes = (fingering) ->
-    return countDistinctNotes(fingering) == chord_note_count
+    return countDistinctNotes(fingering) == chordNoteCount

   mutedMedialStrings = (fingering) ->
     return fingering.fretstring.match(/\dx+\d/)


update options

update options


Require an explicit repo for GitBridge calls.

--- lib/git-bridge.coffee
+++ lib/git-bridge.coffee
@@ -93,14 +93,10 @@
         [__, indexCode, workCode, p] = m
         handler(indexCode, workCode, p)

-  @_checkHealth: (callback, filepath) ->
+  @_checkHealth: (callback) ->
     unless GitCmd
       console.trace("GitBridge method called before locateGitAnd")
       callback(new Error("GitBridge.locateGitAnd has not been called yet"))
-      return false
-
-    unless @getActiveRepo(filepath)
-      callback(new Error("No git repository detected"))
       return false

     return true
@@ -140,8 +136,8 @@
     proc.process.on 'error', (err) ->
       handler(new GitNotFoundError(errMessage.join("\n")), null)

-  @isStaged: (filepath, handler) ->
-    return unless @_checkHealth(handler, filepath)
+  @isStaged: (repo, filepath, handler) ->
+    return unless @_checkHealth(handler)

     staged = true

@@ -161,7 +157,7 @@
     proc = @process({
       command: GitCmd,
       args: ['status', '--porcelain', filepath],
-      options: { cwd: @_repoWorkDir() },
+      options: { cwd: repo.getWorkingDirectory() },
       stdout: stdoutHandler,
       stderr: stderrHandler,
       exit: exitHandler
@@ -170,13 +166,13 @@
     proc.process.on 'error', (err) ->
       handler(new GitNotFoundError, null)

-  @checkoutSide: (sideName, filepath, callback) ->
-    return unless @_checkHealth(callback, filepath)
+  @checkoutSide: (repo, sideName, filepath, callback) ->
+    return unless @_checkHealth(callback)

     proc = @process({
       command: GitCmd,
       args: ['checkout', "--#{sideName}", filepath],
-      options: { cwd: @_repoWorkDir() },
+      options: { cwd: repo.getWorkingDirectory() },
       stdout: (line) -> console.log line
       stderr: (line) -> console.log line
       exit: (code) ->
@@ -189,13 +185,13 @@
     proc.process.on 'error', (err) ->
       callback(new GitNotFoundError)

-  @add: (filepath, callback) ->
-    return unless @_checkHealth(callback, filepath)
+  @add: (repo, filepath, callback) ->
+    return unless @_checkHealth(callback)

     @process({
       command: GitCmd,
       args: ['add', filepath],
-      options: { cwd: @_repoWorkDir() },
+      options: { cwd: repo.getWorkingDirectory() },
       stdout: (line) -> console.log line
       stderr: (line) -> console.log line
       exit: (code) ->


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/camera_flash.coffee
+++ src/camera_flash.coffee
@@ -28,7 +28,9 @@

       I.flashCooldown = I.flashCooldown.approach(0, 1)

-
+  self.bind('flash', function(canvas) {
+    return canvas.fill(I.flashColor);
+  });

   ###*
   A convenient way to set the flash effect instance variables. Alternatively, you can modify them by hand, but


Add strike attribute

Add strike attribute

--- src/trix/config/text_attributes.coffee
+++ src/trix/config/text_attributes.coffee
@@ -19,5 +19,8 @@
   underline:
     style: { "textDecoration": "underline" }
     inheritable: true
+  strike:
+    tagName: "del"
+    inheritable: true
   frozen:
     style: { "backgroundColor": "highlight" }


Get loading indicator to show in Form Builder, fix #2683

--- src/resources-packaged/ops/javascript/orbeon/xforms/LoadingIndicator.coffee
+++ src/resources-packaged/ops/javascript/orbeon/xforms/LoadingIndicator.coffee
@@ -90,6 +90,8 @@
         message ?= DEFAULT_LOADING_TEXT
         OD.setStringValue @loadingOverlay.element, message
         @loadingOverlay.show()
+        # Get loading indicator to show above other dialogs or elements
+        $(@loadingOverlay.element).css('z-index', Globals.lastDialogZIndex + 1)
         @_updateLoadingPosition()

     # Actually hides the loading indicator (no counter)


Fix missing highlighting of monadic function names

Closes Alhadis/language-apl#10 for good this time, I hope.

--- grammars/apl.cson
+++ grammars/apl.cson
@@ -682,7 +682,7 @@
 				10: {name: "punctuation.definition.return-value.begin.apl"}
 				11: {name: "punctuation.definition.return-value.end.apl"}
 				12: {name: "keyword.operator.assignment.apl"}
-				13: {name: "entity.function.name.apl", captures: {0: patterns: [include: "#embolden"]}}
+				13: {name: "entity.function.name.apl", patterns: [include: "#embolden"]}
 				14: {name: "entity.function.axis.apl"}
 				15: {name: "punctuation.definition.axis.begin.apl"}
 				16: {name: "invalid.illegal.extra-characters.apl"}


Update nsL.cson

Update nsL.cson

+ added include to preprocessor rule
| changed preprocessor rule from string to keyword

--- grammars/nsL.cson
+++ grammars/nsL.cson
@@ -5,8 +5,8 @@
 comment: "https://github.com/atom/atom-language-nsl"
 patterns: [
   {
-    match: "(\\b|^\\s*)(?i)#(define|else|endif|if|ifend|macro|macroend|nsis|nsisend|return)\\b"
-    name: "string.compiler.nsl"
+    match: "(\\b|^\\s*)(?i)#(define|else|endif|if|ifend|macro|macroend|nsis|nsisend|return|include)\\b"
+    name: "keyword.control.include.nsl"
   }
   {
     match: "(\\b|^\\s*)(?i)(AddIncludeDir|Include|AddPluginDir|Abort|AddBrandingImage|AddSize|AllowRootDirInstall|AllowSkipFiles|AutoCloseWindow|BGFont|BGGradient|BrandingText|BringToFront|CRCCheck|Call|Caption|ChangeUI|CheckBitmap|ClearErrors|CompletedText|ComponentText|CopyFiles|CreateDirectory|CreateFont|CreateShortCut|DeleteINISec|DeleteINIStr|Delete|DeleteRegKey|DeleteRegValue|DetailPrint|DetailsButtonText|DirText|DirVar|DirVerify|EnableWindow|EnumRegKey|EnumRegValue|Exch|Exec|ExecShell|ExecWait|ExpandEnvStrings|FileBufSize|FileClose|FileErrorText|File|FileOpen|FileReadByte|FileRead|FileRecursive|FileSeek|FileWriteByte|FileWrite|FindClose|FindFirst|FindNext|FindWindow|FlushINI|GetCurInstType|GetCurrentAddress|GetDLLVersion|GetDLLVersionLocal|GetDlgItem|GetErrorLevel|GetFileTime|GetFileTimeLocal|GetFunctionAddress|GetInstDirError|GetLabelAddress|GetTempFileName|HideWindow|Icon|IfAbort|IfErrors|IfFileExists|IfRebootFlag|IfSilent|InitPluginsDir|InstProgressFlags|InstTypeGetText|InstType|InstTypeSetText|InstallButtonText|InstallColors|InstallDir|InstallDirRegKey|IntFmt|IsWindow|LangString|LicenseBkColor|LicenseData|LicenseForceSelection|LicenseLangString|LicenseText|LoadLanguageFile|LockWindow|LogSet|LogText|MessageBox|MiscButtonText|Name|OutFile|Pop|Push|Quit|RMDir|RMDirRecursive|ReadEnvStr|ReadINIStr|ReadRegDWORD|ReadRegStr|Reboot|RegDLL|Rename|RequestExecutionLevel|ReserveFile|ReserveFileRecursive|SearchPath|SectionGetFlags|SectionGetInstTypes|SectionGetSize|SectionGetText|SectionIn|SectionSetFlags|SectionSetInstTypes|SectionSetSize|SectionSetText|SendMessage|SetAutoClose|SetBrandingImage|SetCompress|SetCompressorDictSize|SetCompressor|SetCtlColors|SetCurInstType|SetDatablockOptimize|SetDateSave|SetDetailsPrint|SetDetailsView|SetErrorLevel|SetErrors|SetFileAttributes|SetFont|SetOutPath|SetOverwrite|SetRebootFlag|SetRegView|SetShellVarContext|SetSilent|ShowInstDetails|ShowUninstDetails|ShowWindow|SilentInstall|SilentUninstall|Sleep|SpaceTexts|StrCmp|StrCmpS|StrCpy|StrLen|SubCaption|UnRegDLL|UninstallButtonText|UninstallCaption|UninstallIcon|UninstallSubCaption|UninstallText|VIAddVersionKey|VIProductVersion|WindowIcon|WriteINIStr|WriteRegBin|WriteRegDWORD|WriteRegExpandStr|WriteRegStr|WriteUninstaller|XPStyle)\\b"


Improve default gridcomponent cell rendering

--- framer/Components/GridComponent.coffee
+++ framer/Components/GridComponent.coffee
@@ -93,8 +93,10 @@

 	_defaultRenderCell: (cell, column, row) ->
 		fraction = ((column / @columns) + (row / @rows) / 2)
-		cell.backgroundColor = Color.mix("blue", "purple", fraction, true, "rgb")
+		cell.backgroundColor = "#28affa"
+		cell.hueRotate = column * 20 + (row % @columns) * (20 / (@columns + 1))
 		Utils.labelLayer(cell, "#{row}:#{column}")
+		cell.style.fontSize = "30px"

 	_reset: ->
 		_.invokeMap(@cells, "destroy")


Adds a few more (almost all) component toString methods

--- javascripts/lib/math/expression_components.coffee
+++ javascripts/lib/math/expression_components.coffee
@@ -155,14 +155,19 @@
       updatePower: (power)->
         @klass.build base: @base(), power: power

+      toString: ->
+        "Exp(b: #{@base().toString()}, p: #{@power().toString()})"
+
+
     class_mixer(Exponentiation)

     class Pi extends ExpressionComponent
       toDisplay: -> "π"
-
+      toString: -> "PI()"
     class_mixer(Pi)

     class Addition extends ExpressionComponent
+      toDisplay: -> "Add()"
       isOperator: -> true
     class_mixer(Addition)


handle logging out and add placeholders in Apollos.person

--- tests/mocha/apollos/lib/person.coffee
+++ tests/mocha/apollos/lib/person.coffee
@@ -6,8 +6,16 @@
     describe 'person', ->

       beforeEach ->
-        Meteor.logout() if Meteor.isClient
         Meteor.flush()
+
+      before (done) ->
+        if Meteor.isClient
+          Meteor.logout()
+          Meteor.autorun ->
+            user = Apollos.user()
+            done() if Object.keys(user).length is 0
+        else
+          done()

       it 'should be defined', ->
         assert typeof Apollos.person is 'function'
@@ -33,3 +41,7 @@

             it 'should have no keys', ->
               assert.equal Object.keys(Apollos.person()).length, 0
+
+          describe 'when rock association', ->
+
+            it 'should have some keys'


Not attempting to reorder taxon on create since awesome_nested_set refuses

--- backend/app/assets/javascripts/admin/taxonomy.js.coffee
+++ backend/app/assets/javascripts/admin/taxonomy.js.coffee
@@ -30,7 +30,7 @@
     type: "POST",
     dataType: "json",
     url: base_url.toString(),
-    data: ({"taxon[name]": name, "taxon[parent_id]": new_parent.attr("id"), "taxon[child_index]": position }),
+    data: ({"taxon[name]": name, "taxon[parent_id]": new_parent.attr("id")}),
     error: handle_ajax_error,
     success: (data,result) ->
       node.attr('id', data.id)


Add configuration setting to trust a reverse proxy

--- app.coffee
+++ app.coffee
@@ -51,6 +51,7 @@
 	enableHash: yes
 	deterministic: no
 	ssl: no
+	trustProxy: no
 	i18n:
 		locales: [ 'en', 'de' ]
 		directory: "#{__dirname}/locales"
@@ -75,6 +76,7 @@
 app.engine 'handlebars', do expresshb
 app.set 'view engine', 'handlebars'
 app.set 'views', "#{__dirname}/views"
+app.enable 'trust proxy' if config.trustProxy
 # app.enable 'view cache'

 packageList = null


Panic when executed as root

Panic when executed as root

--- app.coffee
+++ app.coffee
@@ -28,6 +28,10 @@
 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE
 ###

+panic = -> throw new Error "Cowardly refusing to keep the process alive as root"
+if process.getuid?() is 0 or process.getgid?() is 0
+	panic()
+
 exec = (require 'child_process').exec

 serverVersion = (require './package.json').version


Shorten key names

Shorten key names

--- app.coffee
+++ app.coffee
@@ -2,7 +2,12 @@
 Express = require 'express'
 SightengineClient = require 'nudity-filter'

-Sightengine = new SightengineClient(process.env.SIGHTENGINE_USER, process.env.SIGHTENGINE_SECRET)
+TwilioSID = process.env.TWILIO_SID
+TwilioAuthToken = process.env.TWILIO_AUTHTOKEN
+SightEngineUser = process.env.SIGHTENGINE_USER
+SightEngineSecret = process.env.SIGHTENGINE_SECRET
+
+Sightengine = new SightengineClient(SightEngineUser, SightEngineSecret)

 app = Express()

@@ -12,7 +17,7 @@
     to = req.query.From
     from = req.query.To

-    client = Twilio(process.env.TWILIO_SID, process.env.TWILIO_AUTHTOKEN)
+    client = Twilio(TwilioSID, TwilioAuthToken)

     if req.query["MediaUrl0"]?
         Sightengine.checkNudityForURL req.query["MediaUrl0"], (error, result) ->


Disable layout when rendering main.xslt

Disable layout when rendering main.xslt

see e4628ed110008f02d8cb8b28514ec645937b5f5c

--- app.coffee
+++ app.coffee
@@ -423,6 +423,7 @@
 app.get /\/style\/.*/, (req, res) ->
 	checkAuth req, res, (username) ->
 		res.type('text/xsl').render 'main',
+			layout: false
 			title: config.pageTitle || 'Tim’s PackageServer'
 			serverVersion: serverVersion
 			host: config.basePath ? "#{req.protocol}://#{req.header 'host'}"


whitespace

whitespace

--- app.coffee
+++ app.coffee
@@ -7,8 +7,8 @@
 bodyParser = require("body-parser")
 routes = require("./routes/index")
 app = express()
+
 app.set "port", process.env.PORT or 3000
-

 # view engine setup
 app.set "views", path.join(__dirname, "views")


Redundant value existence check

Redundant value existence check

--- client/app/lib/activity/sidebar/activitysideview.coffee
+++ client/app/lib/activity/sidebar/activitysideview.coffee
@@ -104,7 +104,7 @@
       botChannels = socialapi.getPrefetchedData 'bot'
       items.unshift botChannels  if botChannels

-    if items?.length
+    if items.length
     then @renderItems null, items
     else @reload()


Did you know?

Did you know?

--- app.coffee
+++ app.coffee
@@ -11,7 +11,7 @@
   res.send "
   <?xml version=\"1.0\" encoding=\"UTF-8\" ?>
   <Response>
-      <Say>#{_.sample(facts)}</Say>
+      <Say>Did you know, #{_.sample(facts)}</Say>
   </Response>"

 app.listen(3005);


Log stack traces in production

Log stack traces in production

--- app.coffee
+++ app.coffee
@@ -43,6 +43,8 @@
 # production error handler
 # no stacktraces leaked to user
 app.use (err, req, res, next) ->
+  console.log "#{err.status} #{err.message}"
+  console.log err.stack
   res.status(err.status || 500)
   res.render 'error',
     message: err.message,


Allow to bind to a specific IP

Allow to bind to a specific IP

--- app.coffee
+++ app.coffee
@@ -34,6 +34,7 @@

 # default values for configuration
 config.port ?= 9001
+config.ip ?= '0.0.0.0'
 config.packageFolder ?= "#{__dirname}/packages/"
 config.packageFolder += '/' unless /\/$/.test config.packageFolder
 config.enableManualUpdate = on
@@ -356,4 +357,4 @@
 	do res.end

 # Once the package list was successfully scanned once bind to the port
-readPackages -> app.listen config.port
+readPackages -> app.listen config.port, config.ip


redis

redis

--- app.coffee
+++ app.coffee
@@ -14,7 +14,6 @@
 server = require('http').Server(app)
 Redis = require('ioredis')
 redis = new Redis(process.env.REDIS_URL)
-console.log redis
 app.use session {
   store: new RedisStore client: redis
   prefix: CONFIG?.DB?.REDIS?.PREFIX + 'sess:'


fix the youtube regex (master)

fix the youtube regex (master)

--- app.coffee
+++ app.coffee
@@ -67,7 +67,7 @@
       localStorage["input"] = val

     strip: (text) ->
-      pattern = /(((http(s)?:\/\/)?)(www\.)?((youtube\.com\/)|(youtu\.be)|(youtube)).[^ ]+)/g
+      pattern = /(((http(s)?:\/\/)?)(www\.)?((youtube\.com\/)|(youtu\.be)|(youtube)).+[^\s])/g
       matches = text.match pattern

     get_playing: ($ul) ->


If the environment variable RUN_FRONTEND is set, run the web frontend regardless of NODE_ENV

--- app.coffee
+++ app.coffee
@@ -1,10 +1,10 @@
 { createServer } = require 'http'
-{ NODE_ENV, PORT } = process.env
+{ NODE_ENV, PORT, RUN_FRONTEND } = process.env
 PORT or= 8070

 # On production, nginx is used to serve static files
 # In development, web.coffee compiles files on-the-fly and serves them
-server = if NODE_ENV is 'production' then createServer() \
+server = if not RUN_FRONTEND and NODE_ENV is 'production' then createServer() \
          else createServer require './web.coffee'

 require('./websocket')(server)


Minor addition to spec

Minor addition to spec

--- spec/emojis-spec.coffee
+++ spec/emojis-spec.coffee
@@ -51,7 +51,7 @@
   describe '#parse', ->

     beforeEach ->
-      @text = 'Hello :boom: I am a :D. This is no a smiley:) and this is not an emoji:+1:'
+      @text = 'Hello :boom: I am a :D and :\') This is no a smiley:) and this is not an emoji:+1:'

     afterEach ->
       Emojis.useImages = false
@@ -63,10 +63,12 @@
       result.should
         .contain "💥"
         .contain "😃"
+        .contain "😂"
         .and.contain ':)'
         .and.contain ':+1:'
         .and.not.contain ':boom:'
         .and.not.contain ':D'
+        .and.not.contain ":')"

     it 'should parse text to emoji images', ->
       Emojis.useImages = true


fix(editable): added check for editable state for editable observables

--- src/lib/editable.coffee
+++ src/lib/editable.coffee
@@ -7,26 +7,32 @@
     console.error 'Editable field "' + property + '" is not a valid Observable'
     return false

-  Observable self, editing_property, (self[property]() == '' || !self[property]()?)
+  # Editing states:
+  # -1: error
+  # 0: default
+  # 1: editing
+  # 2: success
+  Observable self, editing_property, 0

   self[property].subscribe (new_value) =>
     r = callback new_value, property
-
-    unless r == true
-      self[editing_property] false
-
-  self['edit_' + property] = => self[editing_property] true

   return self

 DelayedSave = (options, self) ->
   (value, prop, delay = 250) =>
     if self.id?
+      if self['editing_' + prop]?
+        self['editing_' + prop] 1
+
       window.timeoutEditor = {} unless window.timeoutEditor?
       window.timeoutEditor[options.api + self.id] = {} unless window.timeoutEditor[options.api + self.id]?

       clearTimeout(window.timeoutEditor[options.api + self.id][prop])
       window.timeoutEditor[options.api + self.id][prop] = setTimeout(=>
+        if self['editing_' + prop]?
+          self['editing_' + prop] 2
+
         edit_value = {}
         edit_value[prop] = value
         self.update edit_value
@@ -43,10 +49,14 @@
     self[options.encrypted_container][prop] = value

     if self.id?
+      self['editing_' + prop] 1
+
       window.timeoutEditor = {} unless window.timeoutEditor?
       clearTimeout(window.timeoutEditor[options.api + self.id])
       window.timeoutEditor[options.api + self.id] = setTimeout(=>
         self.save_encrypted_container()
+
+        self['editing_' + prop] 2

         ## and for verification, save it unencrypted too
         edit_value = {}


Remove dangling log and cleanup whitespaces

--- components/autocomplete/KDAutoCompleteController.coffee
+++ components/autocomplete/KDAutoCompleteController.coffee
@@ -86,7 +86,7 @@
       callback      : =>
         view = @getView()
         view.$input().trigger('focus')
-
+
     dropdownListView.on 'ItemWasAdded', (view, index)=>
       view.registerListener
         KDEventTypes  : 'KDAutoCompleteSubmit'
@@ -106,7 +106,7 @@

     dropdownWrapper.setClass "kdautocomplete hidden #{@getOptions().listWrapperCssClass}"
     KDView.appendToDOMBody dropdownWrapper
-
+
   hideDropdown:->
     dropdownWrapper = @dropdown.getView()
     dropdownWrapper.$().fadeOut 75
@@ -131,7 +131,7 @@
     #   top   : y
     #   left  : x
     # log @dropdown.getListView()
-    # @dropdown.getListView().$().css
+    # @dropdown.getListView().$().css
     #   left  : mainView.getLeftOffset()
     #   top   : mainView.getHeight()-1

@@ -144,42 +144,42 @@
     exactPattern = RegExp('^'+@dropdownPrefix.replace(/[^\s\w]/, '')+'$', 'i')
     exactMatches = []
     inexactMatches = []
-
+
     {itemDataPath,allowNewSuggestions,minSuggestionLength} = @getOptions()
-
+
     data.forEach (datum)=>
       unless @isItemAlreadySelected datum
         match = JsPath.getAt datum, itemDataPath
-
+
         if exactPattern.test match
           exactMatches.push datum
         else
           inexactMatches.push datum
-
+
     if (@dropdownPrefix.length >= minSuggestionLength) and allowNewSuggestions and not exactMatches.length
       @dropdown.getListView().addItemView @getNoItemFoundView()

     data = exactMatches.concat inexactMatches
     @dropdown.instantiateListItems data
     @dropdown.getListView().goDown()
-  #
+  #
   # instantiateDropdownListItems:(items)->
   #   itemClass = @getOptions().itemClass
   #   dropdownListView = @dropdown.getListView()
-  #
+  #
   #   if not itemClass
   #     log 'there is no item class for autocomplete item, will use default one'
   #     itemClass = KDAutoCompleteListItemView
-  #
+  #
   #   for listItem in items
   #     itemInstance = new itemClass {delegate : dropdownListView},listItem
   #     dropdownListView.items.push itemInstance
   #     dropdownListView.appendItem itemInstance
-  #
+  #
   #     itemInstance.registerListener KDEventTypes : 'KDAutoCompleteSubmit', listener : @, callback : @submitAutoComplete
-  #
+  #
   #   dropdownListView.items[0]?.makeItemActive()
-  #
+  #
   submitAutoComplete:(publishingInstance, data)->
     inputView = @getView()
     # log @getOptions().selectedItemsLimit, @selectedItemCounter
@@ -196,9 +196,9 @@
         type      : "mini"
         title   : "You can add up to #{@getOptions().selectedItemsLimit} items!"
         duration  : 4000
-
+
     @hideDropdown()
-
+
   getAutoCompletedItemParent:->
     {outputWrapper} = @getOptions()
     if outputWrapper instanceof KDView
@@ -221,16 +221,16 @@
         if suggested is selected
           return yes
     no
-
+
   addHiddenInputItem:(name, value)->
     @itemWrapper.addSubView @hiddenInputs[name] = new KDInputView
       type          : "hidden"
       name          : name
       defaultValue  : value
-
+
   removeHiddenInputItem:(name)->
     @hiddenInputs[name].remove()
-
+
   addSelectedItem:(name,data)->
     {selectedItemClass} = @getOptions()
     @itemWrapper.addSubView itemView = new selectedItemClass
@@ -239,7 +239,7 @@
       name     : name
     ,data
     itemView.setPartial ""
-
+
   getSelectedItemData:->
     @selectedItemData

@@ -252,7 +252,7 @@
       if selectedData is data
         selectedItemData.splice i,1
         return
-
+
   getCollectionPath:->
     {name} = @getOptions()
     throw new Error 'No name!' unless name
@@ -260,26 +260,26 @@
     collectionName = Inflector.pluralize(leaf)
     path.push collectionName
     path.join('.')
-
+
   addSuggestion:(title)->
     @propagateEvent KDEventType: 'AutocompleteSuggestionWasAdded', title
-
+
   addItemToSubmitQueue:(item,data)->
     data or= item.getData()

     {itemDataPath,form} = @getOptions()
-
+
     if data
       itemValue = if @options.valuesSavedAsString then JsPath.getAt data, itemDataPath else data
     else
       itemValue = item.getOptions().userInput
       data = JsPath itemDataPath, itemValue
       @addSuggestion itemValue
-
+
     return no if @isItemAlreadySelected data

     path = @getCollectionPath()
-
+
     itemName  = "#{name}-#{@selectedItemCounter++}"
     if form
       collection = form.getCustomData path
@@ -321,33 +321,33 @@
     @removeSelectedItemData data
     @selectedItemCounter--
     item.destroy()
-
+
   rearrangeInputWidth:()->
     # mainView = @getView()
     # mainView.$input().width mainView.$input().parent().width() - mainView.$input().prev().width()
-
+
   appendAutoCompletedItem:()->
     @getView().setValue ""
     @getView().$input().trigger "focus"
-
+
   updateDropdownContents:->
     inputView = @getView()
     if inputView.getValue() is ""
       @hideDropdown()
-
+
     if inputView.getValue() isnt "" # and @dropdownPrefix isnt inputView.getValue()
       @dropdownPrefix = inputView.getValue()
       @fetch (data)=>
         @refreshDropDown data
         @showDropdown()
-
+
   keyUpOnInputView:(inputView, event)=>
     return if event.keyCode in [9,38,40] #tab
     @updateDropdownContents()
     # else
     #   log "just wait for a selection"
     no
-
+
   #this one I guess should be overriden
   fetch:(callback)->
     args = {}
@@ -368,19 +368,19 @@
         @dropdown.getListView().addItemView view, 0
       else
         @dropdown.getListView().addItemView view
-
+
   getNoItemFoundView:(suggestion) ->
     {nothingFoundItemClass} = @getOptions()
     view = new nothingFoundItemClass
       delegate: @dropdown.getListView()
       userInput: suggestion or @getView().getValue()
-
+
   showNoDataFound: ->
     noItemFoundView = @getNoItemFoundView()
     @dropdown.removeAllItems()
     @dropdown.getListView().addItemView noItemFoundView
     @showDropdown()
-
+
   destroy:->
     @dropdown.getView().destroy()
     super
@@ -404,7 +404,7 @@
   $input:()->@$().find("input").eq(0)
   getValue:()-> @$input().val()
   setValue:(value)-> @$input().val(value)
-
+
   bindEvents:()->
     super @$input()

@@ -421,7 +421,7 @@
   focus:(pubInst,event)->
     @setClass "focus"
     (@getSingleton "windowController").setKeyView @
-
+
   keyDown:(event)->
     (@getSingleton "windowController").setKeyView @
     switch event.which
@@ -446,9 +446,9 @@

   keyDown: ->
     no
-
+
   makeItemActive: ->
-
+
   destroy: ->
     super no

@@ -459,14 +459,12 @@
   partial: (data) ->
     "Fetching in process..."

-
 class KDAutoCompleteNothingFoundItem extends KDAutocompleteUnselecteableItem
   constructor:->
     super
     @setClass "kdautocompletelistitem no-result"
   partial: (data) ->
     "Nothing found"
-

 class KDAutoCompletedItem extends KDView
   constructor:(options,data)->
@@ -481,7 +479,7 @@

   viewAppended:->
     @setPartial @partial()
-
+
   partial:(data)->
     @getDelegate().getOptions().itemClass::partial @getData()

@@ -512,7 +510,7 @@
         nextItem.makeItemActive()
     else
       @items[0]?.makeItemActive()
-
+
   goUp:()->
     activeItem = @getActiveItem()
     if activeItem.index?
@@ -527,7 +525,7 @@
     active =
       index : null
       item  : null
-    for item,i in @items
+    for item,i in @items
       if item.active
         active.item  = item
         active.index = i
@@ -550,13 +548,13 @@

   mouseEnter:()->
     @makeItemActive()
-
+
   destroy: ->
     super no

   mouseLeave:()->
     @makeItemInactive()
-
+
   makeItemActive:()->
     item.makeItemInactive() for item in @getDelegate().items
     @active = yes
@@ -565,45 +563,24 @@
   makeItemInactive:()->
     @active = no
     @unsetClass "active"
-
+
   click:()->
     @propagateEvent KDEventType: 'KDAutoCompleteSubmit', globalEvent : yes, @data
     no
-
+
   partial:()->
     "
Default item
"

-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
 # SIMPLE AUTOCOMPLETE VIEW
 class KDSimpleAutocomplete extends KDAutoComplete
   addItemToSubmitQueue: (item) ->
     itemValue = JsPath.getAt item.getData(), @getOptions().itemDataPath
     @setValue itemValue
-
+
   keyUp: (event) ->
     return if event.keyCode is 13
     super
-
+
   showNoDataFound: ->
     @dropdown.removeAllItems()
     @hideDropdown()
@@ -616,83 +593,83 @@
       title: ''
     }, options
     super options
-
+
   focus:(pubInst,event)->
     (@getSingleton "windowController").setKeyView @
-
+
   viewAppended: ->
     # log 'view appended'
     @list = new MultipleInputListView delegate: @
     @addSubView @list
-
+
   $input:()-> @$().find("input.main").eq(0)
-
+
   getValues: ->
     @_values
-
+
   rearrangeInputWidth: ->
     no
-
+
   addItemToSubmitQueue: ->
     super
     @inputAddCurrentValue()
-
+
   keyUp: (event) ->
     if event.keyCode is 13
       @inputAddCurrentValue()
-
+
     super
-
+
   inputRemoveValue: (value) ->
     index = @_values.indexOf value
     if index > -1
       @_values.splice index, 1
-
+
     @_inputChanged()
-
+
   clear: ->
     @_values = []
     @removeAllItems()
     @_inputChanged()
-
+
   inputAddCurrentValue: () ->
     value = @$input().val()
     value = $.trim value

     return if value in @_values or value is ''
-
+
     @_values.push value
     @$input().val ''
     @list.addItems [value]
-
+
     @_inputChanged()
-
+
   _inputChanged: ->
     if not @_hiddenInputs
       @_hiddenInputs = []
-
+
     #remove old inputs
     for input in @_hiddenInputs
       input.destroy()
-
+
     inputName = @getOptions().name
     for value, index in @_values
       newInput = new KDInputView type: 'hidden', name: inputName + "[#{index}]", defaultValue: value
       @_hiddenInputs.push newInput
       @addSubView newInput
-
+
     @handleEvent type: 'MultipleInputChanged', values: @getValue()
-
+
   click: (event) ->
     if $(event.target).hasClass 'addNewItem'
       @inputAddCurrentValue()
-
-
+
+
   setDomId:()->
     @$input().attr "id", @getDomId()
     @$input().data "data-id", @getId()
     # @$input().attr "name", @getName()
-
+
   setDomElement: ->
     #
Ryan x

     @domElement = $ "

@@ -702,22 +679,21 @@
         <input type='text' class='main'>
         +

-

"
-
+
 class NoAutocompleteInputView extends KDMultipleInputView

   keyUp: (event) ->
     if event.keyCode is 13
       @inputAddCurrentValue()
-
+
   setDomElement:(cssClass)->
     {placeholder} = @getOptions()
     @domElement = $ "
<input type='text' class='main' placeholder='#{placeholder or ''}' />
"
-
+
   addItemToSubmitQueue: (item) ->
     no
-
+
 class NoAutocompleteMultipleListView extends KDView
   constructor: (options, data) ->
     options ?= {}
@@ -725,27 +701,27 @@
       cssClass: 'common-view input-with-extras'
     options = $.extend defaults, options
     super options, data
-
+
   viewAppended: ->
     {icon,input,button} = @options
-
+
     if icon
       @setClass "with-icon"
       options =
         tagName  : "span"
         cssClass : "icon #{icon}"
       @addSubView @icon   = new KDCustomHTMLView options
-
+
     if input
       @addSubView @input  = new NoAutocompleteInputView input
-
+
     if button
       defaults =
         callback:(event)=>
           event.preventDefault()
           event.stopPropagation()
           @input.inputAddCurrentValue()
-
+
       button = $.extend defaults, button
       @addSubView @button = new KDButtonView button

@@ -753,12 +729,12 @@
   setDomElement: ->
     # Ryan x
     @domElement = $ "

"
-
+
   addItems: (items) ->
     for item in items
       newItem = new MultipleListItemView {delegate: @}, item
       @addItemView newItem
-
+
   removeListItem: (instance) ->
     super instance
     @getDelegate().inputRemoveValue instance.getData()
@@ -767,7 +743,7 @@
   click: (event) ->
     if $(event.target).hasClass 'removeIcon'
       @getDelegate().removeListItem @
-
+
   setDomElement: ->
     @domElement = $ ''
   partial: ->


Fixture hashes depend on line returns

Fixture hashes depend on line returns

--- spec/spec-helper.coffee
+++ spec/spec-helper.coffee
@@ -280,5 +280,10 @@
     event = $.event.fix(event)
     $(this).trigger(event)

-unless fs.md5ForPath(require.resolve('./fixtures/sample.js')) == "dd38087d0d7e3e4802a6d3f9b9745f2b"
+if process.platform is 'win32'
+  unmodifiedHash = '15b13ffbc8d1c1ba5af845e5902ae67c'
+else
+  unmodifiedHash = "dd38087d0d7e3e4802a6d3f9b9745f2b"
+
+unless fs.md5ForPath(require.resolve('./fixtures/sample.js')) is unmodifiedHash
   throw new Error("Sample.js is modified")


Update keymap in spec-helper

Update keymap in spec-helper

--- spec/spec-helper.coffee
+++ spec/spec-helper.coffee
@@ -23,7 +23,7 @@
 fixturePackagesPath = path.resolve(__dirname, './fixtures/packages')
 atom.packages.packageDirPaths.unshift(fixturePackagesPath)
 atom.keymap.loadBundledKeymaps()
-keyBindingsToRestore = null
+keyBindingsToRestore = _.clone(atom.keymap.allBindings())

 $(window).on 'core:close', -> window.close()
 $(window).on 'unload', ->
@@ -52,6 +52,7 @@
   else
     atom.project = new Project(path.join(@specDirectory, 'fixtures'))
   window.project = atom.project
+  atom.keymap.keyBindings = keyBindingsToRestore

   window.resetTimeouts()
   atom.packages.packageStates = {}
@@ -65,9 +66,6 @@
     else
       resolvePackagePath(packageName)
   resolvePackagePath = _.bind(spy.originalValue, atom.packages)
-
-  # used to reset keymap after each spec
-  keyBindingsToRestore = _.clone(atom.keymap.allBindings())

   # prevent specs from modifying Atom's menus
   spyOn(atom.menu, 'sendToBrowserProcess')
@@ -107,7 +105,6 @@
   addCustomMatchers(this)

 afterEach ->
-  atom.keymap.keyBindings = keyBindingsToRestore
   atom.deactivatePackages()
   atom.menu.template = []


Added multilang support in blueprint

Added multilang support in blueprint

--- template/base/config/blueprints.coffee
+++ template/base/config/blueprints.coffee
@@ -13,17 +13,18 @@
 http://links.sailsjs.org/docs/config/blueprints
 ###
 module.exports.blueprints =
-
+
   ###
   NOTE:
   A lot of the configuration options below affect so-called "CRUD methods",
   or your controllers' `find`, `create`, `update`, and `destroy` actions.
-
+
   It's important to realize that, even if you haven't defined these yourself, as long as
   a model exists with the same name as the controller, Sails will respond with built-in CRUD
   logic in the form of a JSON API, including support for sort, pagination, and filtering.
   ###
-
+
+
   # Action blueprints speed up the backend development workflow by eliminating the need
   # to manually bind routes. When enabled, GET, POST, PUT, and DELETE routes will be
   # generated for every one of a controller's actions.
@@ -47,14 +48,22 @@
   # `POST    /email/send/:id?`
   # `PUT     /email/send/:id?`
   # `DELETE  /email/send/:id?`
-  #
-  #
+
+  # Activate multilang support
+  # if is true the routes are binds to languages path defined in config/locales, e.g.:
+  #    /en/user
+  #    /es/user
+  # Automatically request on route '/' is redirect to aproppiate lang.
+  # This is based on localize header. e.g.:
+  #    /user/1 is fixed to /en/user/1
+  i18n: true
+
   # `actions` are enabled by default, and can be OK for production-- however,
   # if you'd like to continue to use controller/action autorouting in a production deployment,
   # you must take great care not to inadvertently expose unsafe/unintentional controller logic
   # to GET requests.
   actions: true
-
+
   # RESTful Blueprints
   # (`sails.config.blueprints.rest`)
   #
@@ -73,14 +82,14 @@
   # in a production scenario, as long you take standard security precautions
   # (combine w/ policies, etc.)
   rest: true
-
+
   # Shortcut blueprints are simple helpers to provide access to a controller's CRUD methods
   # from your browser's URL bar.  When enabled, GET, POST, PUT, and DELETE routes will be generated
   # for the controller's`find`, `create`, `update`, and `destroy` actions.
   #
   # `shortcuts` are enabled by default, but should be disabled in production.
   shortcuts: true
-
+
   # An optional mount path for all blueprint routes on a controller, including `rest`,
   # `actions`, and `shortcuts`.  This allows you to take advantage of blueprint routing,
   # even if you need to namespace your API methods.
@@ -97,7 +106,7 @@
   #
   # By default, no prefix is used.
   prefix: ""
-
+
   # Whether to pluralize controller names in blueprint routes.
   #
   # (NOTE: This only applies to blueprint autoroutes, not manual routes from `sails.config.routes`)
@@ -108,10 +117,10 @@
   # PUT    /foos/:id?
   # DELETE /foos/:id?
   pluralize: false
-
+
   # Whether the blueprint controllers should populate model fetches with
   # data from other models which are linked by associations
-
+
   # If you have a lot of data in one-to-many associations, leaving this on
   # may result in very heavy api calls
   populate: true


handle hashchange event.

handle hashchange event.

--- js/umobi.core.coffee
+++ js/umobi.core.coffee
@@ -4,6 +4,22 @@
 define ["depend!jquery.hashchange[jquery]"], ->
   uMobi =
     Env: { }
+
+
+  uMobi.showPage = (hash) ->
+    # hide current active page
+    $('.ui-page-active').hide().removeClass('ui-page-active')
+
+    # show first page if page not found.
+    $page = $(hash)
+    if $page.get(0)
+      $page.show().addClass('ui-page-active')
+    else
+      $($('[data-role="page"]').get(0)).show().addClass('ui-page-active')
+
+  uMobi.handleHashChange = (e) ->
+    hash = location.hash
+    uMobi.showPage(hash)

   # uMobi
   uMobi.initPageContainer = (el) ->
@@ -46,7 +62,12 @@
     $h.addClass('ui-fixed-hd') if $h.data 'fixed'
     $f.addClass('ui-fixed-ft') if $f.data 'fixed'

+
+  # Page Initialization
   $ ->
+    $(window).on 'hashchange', (e) ->
+      uMobi.handleHashChange(e)
+
     $(document).trigger('pageinit')

     # find static pages and initialize them
@@ -63,10 +84,5 @@
       $a.click (e) ->
         href = $(this).attr('href')
         if href.match(/^#\w+/)
-          $activePage = $('.ui-page-active')
-          $page = $(href)
-          # got page
-          if $page.get(0)
-            $page.show().addClass('ui-page-active')
-            $activePage.hide().removeClass('ui-page-active')
+          uMobi.showPage(href)
   return uMobi


Le't find out why prices are not ordered as expected when run on travis.

--- src/spec/integration/impex.spec.coffee
+++ src/spec/integration/impex.spec.coffee
@@ -60,11 +60,14 @@
       expect(result[1]).toBe '[row 4] New product created.'
       file = '/tmp/impex.csv'
       @exporter.export(csv, file)
-      .then (result) ->
+      .then (result) =>
         console.log "export", result
         expect(result).toBe 'Export done.'
+        @client.products.all().fetch()
+      .then (res) ->
+        console.log "products %j", res.body
         fs.readFileAsync file, {encoding: 'utf8'}
-      .then (content) =>
+      .then (content) ->
         console.log "export file content", content
         expect(content).toMatch header
         expect(content).toMatch p1


'fix PATH'

'fix PATH'

--- lib/command-output-view.coffee
+++ lib/command-output-view.coffee
@@ -1,5 +1,5 @@
 {View, EditorView} = require 'atom'
-{spawn} = require 'child_process'
+{spawn, exec} = require 'child_process'
 ansihtml = require 'ansi-html-stream'
 readline = require 'readline'
 {addClass, removeClass} = require 'domutil'
@@ -31,8 +31,9 @@

   initialize: ->
     @userHome = process.env.HOME or process.env.HOMEPATH or process.env.USERPROFILE;
-    #FIXME how to set ENV, settings? profile?
-    process.env.PATH = process.env.PATH + ':/usr/local/bin'
+    cmd = 'test -e /etc/profile && source /etc/profile;test -e ~/.profile && source ~/.profile; node -pe "JSON.stringify(process.env)"'
+    exec cmd, (code, stdout, stderr) ->
+      process.env = JSON.parse(stdout)
     atom.workspaceView.command "cli-status:toggle-output", =>
       @toggle()


Minor fix on task empty style

Minor fix on task empty style

--- lib/commands/style-line.coffee
+++ lib/commands/style-line.coffee
@@ -99,6 +99,9 @@
     selection.setBufferRange(range) # reselect the spreviously selected range

   insertEmptyStyle: (selection, data) ->
+    if @style.captureBefore
+      data["captureBefore"] = data[@style.captureBefore]
+
     selection.insertText(utils.template(@style.before, data))
     position = selection.cursor.getBufferPosition()
     selection.insertText(utils.template(@style.after, data))


fixed deselection of preselected element

--- packages/custom/transparency/coffee/public/controllers/flowController.coffee
+++ packages/custom/transparency/coffee/public/controllers/flowController.coffee
@@ -294,7 +294,9 @@
                 $scope.error = res.data
                 #console.log "resolve dataPromise after exception"
                 dataPromise.resolve()
-
+        else
+            stopLoading()
+            $scope.error = "nothing selected"

     checkMaxLength = (data) ->
         ###if data.nodes.length > $scope.maxNodes


fix jquery

fix jquery

--- grunt/options/copy.coffee
+++ grunt/options/copy.coffee
@@ -21,7 +21,7 @@
         expand: yes
         flatten: yes
         cwd: "client/vendor/bower"
-        src: ["jquery/jquery.js", "handlebars/handlebars.js", "lodash/dist/lodash.js"]
+        src: ["jquery/dist/jquery.js", "handlebars/handlebars.js", "lodash/dist/lodash.js"]
         dest: "public"
       }
       {


alternate wiring mode

alternate wiring mode

--- src/javascript/game/metroid_delegate.coffee
+++ src/javascript/game/metroid_delegate.coffee
@@ -29,9 +29,9 @@
 #

 # Per time slice, bundle ticks and controller events into an "input" structure
-inputBundler = (defaultInput) ->
+inputBundler = (baseInput) ->
   (events) ->
-    input = defaultInput
+    input = baseInput
     for e in events
       switch e.get('type')
         when 'Tick'
@@ -126,36 +126,65 @@
       uiConfig: uiConfig
       uiState: uiState

-
-    ########################################################################################
-    #
-    # SIGNALLY STUFF
-    #
-
-    tick = @time.map((dt) -> Map(type:'Tick',dt:dt))
-
-    playerControl = @player1KbController.merge(@player1GpController)
-      .dropRepeats(Immutable.is)
-      .map((event) -> event.set('tag','player1'))
-
-    adminControl = @adminController
-      .dropRepeats(Immutable.is)
-      .map((event) -> event.set('tag','admin'))
-
-
-    # For each time slice, smush all events into a composite 'input' structure
-    defaultInput = Immutable.fromJS
+    @defaultInput = Immutable.fromJS
       controllers: {}
       dt: 0
       static:
         worldMap: worldMap

+
+    # @_wireUp_production()
+    @_wireUp_development()
+
+  _wireUp_production: ->
+    tick = @time.map((dt) -> Map(type:'Tick',dt:dt))
+
+    playerControl = @player1KbController.merge(@player1GpController)
+      .dropRepeats(Immutable.is)
+      .map((event) -> event.set('tag','player1'))
+
+    input = tick
+      .merge(playerControl)
+      .sliceOn(tick)             # batch up the events in an array and release that array on arrival of dt event
+      .map(inputBundler(@defaultInput)) # compile the 'input' structure (controller states, dt and static game data)
+
+    updateGame = (input,s) ->
+      [s1,_] = TheGame.update(s,input)
+      return s1
+
+    state = input
+      .foldp(updateGame, TheGame.initialState())
+      .dropRepeats(Immutable.is)  # (if a new history is identical to the prior history, send no change)
+
+    # Funnel game updates into the view:
+    state.subscribe (s) =>
+      @viewMachine.update(s.get('gameState'))
+
+
+  _wireUp_development: ->
+    ########################################################################################
+    #
+    # SIGNALLY STUFF
+    #
+
+    tick = @time.map((dt) -> Map(type:'Tick',dt:dt))
+
+    playerControl = @player1KbController.merge(@player1GpController)
+      .dropRepeats(Immutable.is)
+      .map((event) -> event.set('tag','player1'))
+
+    adminControl = @adminController
+      .dropRepeats(Immutable.is)
+      .map((event) -> event.set('tag','admin'))
+
+
+    # For each time slice, smush all events into a composite 'input' structure
     input = tick
       .merge(playerControl)
       .merge(adminControl)
       .merge(@adminUISignal)     # all dt, keybd and gp events into one stream
       .sliceOn(tick)             # batch up the events in an array and release that array on arrival of dt event
-      .map(inputBundler(defaultInput)) # compile the 'input' structure (controller states, dt and static game data)
+      .map(inputBundler(@defaultInput)) # compile the 'input' structure (controller states, dt and static game data)

     # The current state of the administrative controls:


tests: Kill skipped Coordinator tests

tests: Kill skipped Coordinator tests

The same things are exercised in fbp-spec test,
in a way that works and makes more sense to keep stable.

--- spec/coordinator.coffee
+++ spec/coordinator.coffee
@@ -42,59 +42,3 @@
             done()
           first.start (err) -> chai.expect(err).to.be.a 'null'

-      describe.skip 'sending data into participant input queue', ->
-        it 'should receive results on output queue', (done) ->
-          @timeout 4000
-          first = participants.Hello address, 'hello-first'
-          coordinator.once 'participant-added', (participant) ->
-            id = first.definition.id
-            coordinator.subscribeTo id, 'out', (msg) ->
-              chai.expect(msg.data).to.equal 'Hello Jon'
-              done()
-            coordinator.sendTo id, 'name', 'Jon'
-          first.start (err) -> chai.expect(err).to.be.a 'null'
-
-      describe.skip 'sending data to participant connected to another', ->
-        it 'should receive results at end of flow', (done) ->
-          @timeout 4000
-          first = participants.Hello address, 'hello1'
-          second = participants.Hello address, 'hello2'
-          participantsNumber = 0
-          coordinator.on 'participant-added', (participant) ->
-            participantsNumber = participantsNumber+1
-            return if participantsNumber != 2
-            coordinator.connect first.definition.id, 'out', second.definition.id, 'name'
-            coordinator.subscribeTo second.definition.id, 'out', (msg) ->
-              chai.expect(msg.data).to.equal 'Hello Hello Johnny'
-              done()
-            coordinator.sendTo first.definition.id, 'name', 'Johnny'
-          first.start (err) -> chai.expect(err).to.be.a 'null'
-          second.start (err) -> chai.expect(err).to.be.a 'null'
-
-      describe.skip 'loading graph as json', ->
-        it 'should not return error', (done) ->
-          coordinator.loadGraphFile 'graphs/hello.json', (err) ->
-            chai.expect(err).to.be.a 'null'
-            done()
-
-        it 'should set up participants', (done) ->
-          participantsNumber = 0
-          coordinator.on 'participant-added', (participant) ->
-            participantsNumber = participantsNumber+1
-            return if participantsNumber != 3
-            done()
-          coordinator.loadGraphFile 'graphs/hello.json', (err) ->
-            chai.expect(err).to.be.a 'null'
-
-        it 'should set up connections', (done) ->
-          @timeout 4000
-          coordinator.loadGraphFile 'graphs/hello.json', (err) ->
-            chai.expect(err).to.be.a 'null'
-            helloCs = coordinator.participantsByRole 'helloC'
-            helloAs = coordinator.participantsByRole 'helloA'
-            coordinator.subscribeTo helloCs[0], 'out', (msg) ->
-              return if msg.data == 'Hello Hello Hello World' # IIP
-              chai.expect(msg.data).to.equal 'Hello Hello Hello JSON'
-              done()
-            coordinator.sendTo helloAs[0], 'name', 'JSON'
-


modules that export a list of functions

modules that export a list of functions

--- spec/define_spec.coffee
+++ spec/define_spec.coffee
@@ -64,7 +64,7 @@
                 non.function2().should.equal 'RESULT2'

-        it 'has get() in scope to (at require time) return previously defined mock object',
+        it 'has get() in scope (at require time) to return previously defined mock object',

             ipso (Define) ->

@@ -81,7 +81,17 @@

-    context 'modules that export an object', ->
+    context 'modules that export a list of functions', ->
+
+        it 'can be defined',
+
+            ipso (Define) ->
+
+                Define missing: ->
+                m = require 'missing'
+
+
+
     context 'modules that export a class', ->


Fix #973 - Uncaught TypeError: args.split is not a function

--- lib/script-options-view.coffee
+++ lib/script-options-view.coffee
@@ -84,11 +84,11 @@
     (replaces['`#match' + (Object.keys(replaces).length + 1) + '`'] = match) for match in matches

     # replace strings
-    args = (args.replace(new RegExp(part, 'g'), match) for match, part of replaces)
+    (args = args.replace(new RegExp(part, 'g'), match)) for match, part of replaces
     split = (item for item in args.split ' ' when item isnt '')

     replacer = (argument) ->
-      argument = (argument.replace(match, replacement) for match, replacement of replaces)
+      (argument = argument.replace(match, replacement)) for match, replacement of replaces
       argument

     # restore strings, strip quotes


Ref: ElementとHTMLElementの優先順位を逆転

Ref: ElementとHTMLElementの優先順位を逆転

--- src/DOMExtention.coffee
+++ src/DOMExtention.coffee
@@ -1,7 +1,7 @@
 ###
   DOM Extention
 ###
-ele = HTMLElement ? Element
+ele = Element ? HTMLElement

 for key of ele::
   if key is "classList"


:bug: Fix specs

:bug: Fix specs

--- spec/helper-spec.coffee
+++ spec/helper-spec.coffee
@@ -86,7 +86,7 @@
           expect(range[0][0]).toEqual(1)
           expect(range[0][1]).toEqual(0)
           expect(range[1][0]).toEqual(1)
-          expect(range[1][1]).toEqual(41)
+          expect(range[1][1]).toEqual(40)
     it 'returns a range (array) with some valid points and provided colStart', ->
       waitsForPromise ->
         atom.workspace.open("#{__dirname}/fixtures/something.js").then ->
@@ -98,7 +98,7 @@
           expect(range[0][0]).toEqual(1)
           expect(range[0][1]).toEqual(4)
           expect(range[1][0]).toEqual(1)
-          expect(range[1][1]).toEqual(41)
+          expect(range[1][1]).toEqual(40)

   describe '::parse', ->
     it 'cries when no argument is passed', ->


Give specs window a keymap with the default bindings

--- spec/spec-helper.coffee
+++ spec/spec-helper.coffee
@@ -13,6 +13,10 @@

 afterEach ->
   $('#jasmine-content').empty()
+
+specsKeymap = new GlobalKeymap
+specsKeymap.bindDefaultKeys()
+$(window).on 'keydown', (e) -> specsKeymap.handleKeyEvent(e)

 # Use underscore's definition of equality for toEqual assertions
 jasmine.Env.prototype.equals_ = _.isEqual


only redraw on sync

only redraw on sync

--- lib/assets/javascripts/stark/model_binding.js.coffee
+++ lib/assets/javascripts/stark/model_binding.js.coffee
@@ -40,10 +40,10 @@
       @log "redrawing view: ", @
       @redraw()

-    @onR view, "change", ->
-      @log "redraw handler fired (change) due to model binding on: ", model
-      @log "redrawing view: ", @
-      @redraw()
+    # @onR view, "change", ->
+    #   @log "redraw handler fired (change) due to model binding on: ", model
+    #   @log "redrawing view: ", @
+    #   @redraw()

     @onR view, "destroy", ->
       @log "redraw handler fired (destroy) due to model binding on: ", model


Handsontable load data working

Handsontable load data working

--- src/main/js/worksheet/TableWorksheet.coffee
+++ src/main/js/worksheet/TableWorksheet.coffee
@@ -53,6 +53,7 @@
     self = this
     @table.addHook 'afterChange', (changes, source) ->
       console.log 'afterChange', changes
+      if !changes or changes.length == 0 then return
       firstChange = changes[0]
       [rowIndex, propertyName, oldValue, newValue] = firstChange
       row = self.data[rowIndex]
@@ -78,23 +79,12 @@
     @el.find('tbody td, tbody th.value').empty()

   loadText: (text) ->
-    @clear()
-    @_loadTable @loader.parseDefinitions(text)
+    defs = @loader.parseDefinitions(text)
+    @data = @_dataFromDefs defs
+    @table.loadData @data
     @loader.loadDefinitions text

-  loadRow: (rowIndex, name, formula) ->
-    row = new Row(@el.find("tbody tr").eq(rowIndex))
-    row.setName name
-    row.setFormula formula
-
-  _loadTable: (defs) -> @loadRow(i, def.name, def.expr.text) for def, i in defs
-
-  _parseTable:  ->
-    sheetRows = @el.find('tr')
-    self = this
-    sheetRows.each ->
-      row = new Row($(this))
-      self.updateFormula row.name(), row.formula()
+  _dataFromDefs: (defs) -> defs.map (d) -> {name: d.name, formula: d.expr.text, value: null}

   _updateTable: (name, value) ->
     @_rowForName(name).value = htmlFor(value)


Removed AGM redirect

Removed AGM redirect

--- controllers/index.coffee
+++ controllers/index.coffee
@@ -65,9 +65,6 @@
             res.redirect 301, '//' + res.locals.page.url if req.header('host').match /(cdn.enactuslse|sifelse)/i
             next()

-        @app.get '/agm', (req, res, next) ->
-            res.redirect '/what-is-new/events#agm'
-
     _notFound: (req, res, next) ->
         res.locals.page.title = "Oops, we can't find what you are looking for! - #{res.locals.page.title}"
         res.status 404


Fixes on formHandler

Fixes on formHandler


Fixed android testing error when installing handling

--- lib/testing/env/android.coffee
+++ lib/testing/env/android.coffee
@@ -194,7 +194,10 @@
         onLog: (msg) ->
             env.onLog msg, androidProcess
     , (err) ->
-        env.onExit err
+        if env.onExit
+            env.onExit err
+        else
+            callback err
     return

 listenOnTests = (env, logsReader, callback) ->


Force config init in specs

Force config init in specs

--- spec/minimap-editor-view-spec.coffee
+++ spec/minimap-editor-view-spec.coffee
@@ -10,7 +10,8 @@
   afterEach -> minimapView?.detach()

   beforeEach ->
-    atom.config.set 'editor.useReactEditor', false
+    atom.config.set 'minimap.scaleY', 0.16
+    atom.config.set 'minimap.scaleX', 0.2

     runs ->
       atom.workspaceView = new WorkspaceView


Split test

Split test

--- test/generators/relational/self.coffee
+++ test/generators/relational/self.coffee
@@ -30,7 +30,7 @@

   describe "hasMany (cache: #{true}, query_cache: #{options.query_cache}, embed: #{options.embed})", ->

-    before (done) -> return done() unless options.before; options.before([Flat, Reverse, ForeignReverse, Owner], done)
+    before (done) -> return done() unless options.before; options.before([SelfReference], done)
     after (done) -> callback(); done()
     beforeEach (done) ->
       MODELS = {}


Resurrection: Create and get layout size data by IDELayoutManager

--- client/ide/lib/index.coffee
+++ client/ide/lib/index.coffee
@@ -1269,6 +1269,9 @@
   getWorkspaceSnapshot: -> @layoutManager.createLayoutData()

+  getLayoutSizeData: -> @layoutManager.createLayoutSizeData()
+
+
   changeActiveTabView: (paneType) ->

     if paneType is 'terminal'


styleguide: Moved code one line away from method

--- client/ide/lib/index.coffee
+++ client/ide/lib/index.coffee
@@ -356,6 +356,7 @@
    * @param {Array<string>} files - A list of file paths.
   ###
   openFiles: (files) ->
+
     unless files
       return kd.error "IDEAppController#openFiles: Called with empty files"


test_panelframe: remove extraneous commas

--- test/panelframe/test_panelframe.coffee
+++ test/panelframe/test_panelframe.coffee
@@ -6,15 +6,15 @@

 # Example 2: xNA box, and thicker hlines (underneath vlines)
 mychart2 = panelframe({
-    xNA:true,
-    title:"X-axis NAs",
-    ylim:[0.5,3.5],
-    yticks:[1,2,3],
-    yticklab:["A","H","B"],
-    ylab: "Group",
+    xNA:true
+    title:"X-axis NAs"
+    ylim:[0.5,3.5]
+    yticks:[1,2,3]
+    yticklab:["A","H","B"]
+    ylab: "Group"
     hlineOpts:
-        width:4,
-        color:"#999",
+        width:4
+        color:"#999"
     v_over_h:true})
 mychart2(d3.select("div#chart2"))

@@ -36,36 +36,36 @@

 # Example 3: yNA box and thicker vlines
 mychart3 = panelframe({
-    yNA:true,
-    title:"Y-axis NAs",
+    yNA:true
+    title:"Y-axis NAs"
     vlineOpts:
-        width:4,
-        color:"#999",
-    xlim:[0.5,3.5],
-    xticks:[1,2,3],
-    xticklab:["A", "H", "B"],
+        width:4
+        color:"#999"
+    xlim:[0.5,3.5]
+    xticks:[1,2,3]
+    xticklab:["A", "H", "B"]
     xlab:"Group"})
 mychart3(d3.select("div#chart3"))

 # Example 4: both xNA and yNA boxes, no vertical lines
 mychart4 = panelframe({
-    xNA:true,
-    yNA:true,
-    title:"X- and Y-axis NAs",
+    xNA:true
+    yNA:true
+    title:"X- and Y-axis NAs"
     vlineOpts:
-        width:0,
+        width:0
         color:null})
 mychart4(d3.select("div#chart4"))

 # Example 5: both xNA and yNA boxes, dark lines
 mychart5 = panelframe({
-    xNA:true,
-    yNA:true,
-    title:"X- and Y-axis NAs + dark grid",
+    xNA:true
+    yNA:true
+    title:"X- and Y-axis NAs + dark grid"
     vlineOpts:
-        width:4,
-        color:"#999",
+        width:4
+        color:"#999"
     hlineOpts:
-        width:4,
+        width:4
         color:"#999"})
 mychart5(d3.select("div#chart5"))


removed unused line

removed unused line

--- test/spec/renuo_cms_client_spec.coffee
+++ test/spec/renuo_cms_client_spec.coffee
@@ -2,7 +2,6 @@

 describe 'RenuoCmsClient', ->
   renuoCmsClient = undefined
-  jasmine.getJSONFixtures().fixturesPath = '/Users/nicolaseckhart/Workspace/renuo-cms-client/test/fixtures/';

   beforeEach ->
     renuoCmsClient = new RenuoCmsClient()


Comment on 500 errors

Comment on 500 errors

--- src/microservice.coffee
+++ src/microservice.coffee
@@ -288,6 +288,8 @@

     if req.log
       req.log.error {err: err}, "Error"
+
+    # Report server errors; these are something we have to fix

     if res.statusCode >= 500 and res.statusCode < 600
       @slackMessage 'error', "#{err.name}: #{err.message}", ":bomb:", (err) =>


Use constructor.name instead of hard-coded string

--- src/edit-session.coffee
+++ src/edit-session.coffee
@@ -58,7 +58,7 @@
       @id = guid.create().toString()
       displayBuffer ?= new DisplayBuffer({buffer, tabLength})
       @state = site.createDocument
-        deserializer: 'EditSession'
+        deserializer: @constructor.name
         version: @constructor.version
         id: @id
         bufferId: buffer.id


keep the existing GET method temporarily

to avoid any problems due to deployment in the wrong order

--- app.coffee
+++ app.coffee
@@ -38,6 +38,9 @@
 		next new Error("invalid doc id")

 app.get    '/project/:project_id/doc/:doc_id',                          HttpController.getDoc
+# temporarily keep the GET method for backwards compatibility
+app.get    '/project/:project_id/doc',                                  HttpController.getProjectDocsAndFlushIfOld
+# will migrate to the POST method of get_and_flush_if_old instead
 app.post   '/project/:project_id/get_and_flush_if_old',                 HttpController.getProjectDocsAndFlushIfOld
 app.post   '/project/:project_id/clearState',                           HttpController.clearProjectState
 app.post   '/project/:project_id/doc/:doc_id',                          HttpController.setDoc


removed email field from angellist profile data

--- app.coffee
+++ app.coffee
@@ -38,7 +38,6 @@
     save =
       angellist_id: profile.id
       name: profile._json.name
-      email: profile._json.email
       avatar_url: profile._json.image
     options = upsert: true


Improve express error handling

Improve express error handling

--- app.coffee
+++ app.coffee
@@ -571,13 +571,17 @@
 				if isAccessible username, req.params[0], req.params[1]
 					debug "#{username} downloaded #{req.params[0]}/#{req.params[1].toLowerCase()}"
 					logDownload req.params[0], req.params[1]
-					res.download "#{config.packageFolder}/#{req.params[0]}/#{req.params[1].toLowerCase()}.tar", "#{req.params[0]}_v#{req.params[1]}.tar", (err) -> res.sendStatus 404 if err?
+					res.download "#{config.packageFolder}/#{req.params[0]}/#{req.params[1].toLowerCase()}.tar", "#{req.params[0]}_v#{req.params[1]}.tar", (err) ->
+						if err?
+							res.sendStatus 404 unless res.headersSent
+
+							do res.end
 				else
 					debug "#{username} tried to download #{req.params[0]}/#{req.params[1].toLowerCase()}"
 					askForCredentials req, res
 			else
 				res.sendStatus 404
-
+
 	checkAuth req, res, callback

 # allow download without version number
@@ -589,8 +593,16 @@
 		return
 	res.redirect 301, "#{host}/#{req.params[0]}/#{versionNumber.toLowerCase().replace /[ ]/g, '_'}"

-app.get '/app.coffee', (req, res) -> res.type('txt').sendFile "#{__dirname}/app.coffee", (err) -> res.sendStatus 404 if err?
-
+app.get '/app.coffee', (req, res) ->
+	res.type('txt').sendFile "#{__dirname}/app.coffee", (err) ->
+		if err
+			if err.code is 'ECONNABORT' and res.statusCode is 304
+				debug 'Request aborted, cached'
+			else
+				res.sendStatus 404 unless res.headersSent
+
+			do res.end
+
 app.get /\/style\/.*/, (req, res) ->
 	res.type('xml').render 'main',
 		title: config.pageTitle || 'Tim’s PackageServer'


Fixed bug where elapsed time would still end up in block time in Raw Data

--- src/models/punch.coffee
+++ src/models/punch.coffee
@@ -111,7 +111,7 @@
         hours = Math.floor @elapsed
         minutes = Math.round((@elapsed - hours) * 60)
         minute_str = if minutes < 10 then "0#{minutes}" else minutes
-        row[headers.blockTime] = "#{hours}:#{minute_str}:00"
+        row[headers.totalTime] = "#{hours}:#{minute_str}:00"
     row[headers.notes] = @notes
     if @mode is 'vacation' or
         @mode is 'sick' or


Alphabetize extension arrays

Alphabetize extension arrays

--- src/packages/tree-view/src/file-view.coffee
+++ src/packages/tree-view/src/file-view.coffee
@@ -8,16 +8,16 @@
 class FileView extends View

   @COMPRESSED_EXTENSIONS: [
-    '.zip'
+    '.gz'
     '.jar'
     '.tar'
-    '.gz'
+    '.zip'
   ]

   @IMAGE_EXTENSIONS: [
+    '.gif'
     '.jpeg'
     '.jpg'
-    '.gif'
     '.png'
   ]


Remove the lobby if being configured

Remove the lobby if being configured

--- server/serversystem/manager.coffee
+++ server/serversystem/manager.coffee
@@ -32,6 +32,8 @@
     return if !sock?
     console.log "told server "+serv._id+" to kill instance "+alob.id
     sock.send "shutdownServer|"+alob.id
+    if lob.status is 2
+      lobbies.remove({_id: id})
   else
     lobbies.remove {_id: id}


align drill tool parsing with gerber tool parsing

--- src/drill-parser.coffee
+++ src/drill-parser.coffee
@@ -58,14 +58,15 @@

     # tool definition
     else if ( code = block.match(/T\d+/)?[0] )
-      # tool definition
-      toolCode = "T#{Number code[1..]}"
+      # remove leading zeros from tool code
+      code = code[0] + code[2..] while code[1] is '0'
+      # issue a create tool command or a set tool command
       if ( dia = block.match(/C[\d\.]+(?=.*$)/)?[0] )
         dia = Number dia[1..]
         command.tool = {}
-        command.tool[toolCode] = { dia: dia }
+        command.tool[code] = { dia: dia }
       else
-        command.set = { currentTool: toolCode }
+        command.set = { currentTool: code }

     # allow this to be tacked on the end of a command to be lenient
     # we're assuming trailing zero suppression, so we only care if the opposite


:memo: Drop redundant notification

:memo: Drop redundant notification

--- src/notification.coffee
+++ src/notification.coffee
@@ -18,10 +18,10 @@

   getOptions: -> @options

-  # Public: Retrieves the {String} notification type.
+  # Public: Retrieves the {String} type.
   getType: -> @type

-  # Public: Retrieves the {String} notification message.
+  # Public: Retrieves the {String} message.
   getMessage: -> @message

   getTimestamp: -> @timestamp


surgery ops to the old coding

surgery ops to the old coding

--- library/nucleus/service.coffee
+++ library/nucleus/service.coffee
@@ -131,7 +131,7 @@
         instance kernel, service; return service

     # This method implements a clever little lazy initialize system
-    # for the services. Basically, if you supply a fucntion to this
+    # for the services. Basically, if you supply a function to this
     # method invocation, it will save it as a lazy config function,
     # and execute it when the service is about to be spawned. But
     # if you don't supply anything - it returns an executor method
@@ -193,46 +193,6 @@
         assert _.isString(location), noLocation
         assert not _.isEmpty(location), isEmpty
         @$location = location.toString(); this
-
-    # This is a very basic method that adds the specified regular
-    # expression pattern to the list of permitted resource patterns.
-    # The patterns are associated with a service class, not object.
-    # Supports implicit extraction of captured groups in the match.
-    # Use this to configure what resources should match with service.
-    @resource: (pattern) ->
-        associate = "Associating %s resource with %s"
-        identify = @identify().underline.toString()
-        r = (s) -> new RegExp "^#{RegExp.escape(s)}$"
-        pattern = try r pattern if _.isString pattern
-        inspected = try pattern.unescape()?.underline
-        inspected = pattern unless _.isString inspected
-        source = not _.isEmpty try pattern.source or null
-        notReg = "the #{inspected} is not a valid regexp"
-        assert _.isRegExp(pattern) and source or 0, notReg
-        assert @resources = (@resources or []).concat pattern
-        assert @resources = _.unique this.resources or []
-        logger.debug associate, inspected, identify
-        return this # return itself for chaining...
-
-    # This is a very basic method that adds the specified regular
-    # expression pattern to the list of permitted domain patterns.
-    # The patterns are associated with a service class, not object.
-    # Supports implicit extraction of captured groups in the match.
-    # Use this to configure what domains should match with service.
-    @domain: (pattern) ->
-        associate = "Associating %s domain with %s"
-        identify = @identify().underline.toString()
-        r = (s) -> new RegExp "^#{RegExp.escape(s)}$"
-        pattern = try r pattern if _.isString pattern
-        inspected = try pattern.unescape()?.underline
-        inspected = pattern unless _.isString inspected
-        source = not _.isEmpty try pattern.source or null
-        notReg = "the #{inspected} is not a valid regexp"
-        assert _.isRegExp(pattern) and source or 0, notReg
-        assert @domains = (@domains or []).concat pattern
-        assert @domains = _.unique this.domains or []
-        logger.debug associate, inspected, identify
-        return this # return itself for chaining...

     # This method should process the already matched HTTP request.
     # But since this is an abstract base class, this implementation
@@ -300,3 +260,43 @@
         response.writeHead 500, http.STATUS_CODES[500]
         render = format template, plain, error.stack
         response.end render; return next undefined
+
+    # This is a very basic method that adds the specified regular
+    # expression pattern to the list of permitted resource patterns.
+    # The patterns are associated with a service class, not object.
+    # Supports implicit extraction of captured groups in the match.
+    # Use this to configure what resources should match with service.
+    @resource: (pattern) ->
+        associate = "Associating %s resource with %s"
+        identify = @identify().underline.toString()
+        r = (s) -> new RegExp "^#{RegExp.escape(s)}$"
+        pattern = try r pattern if _.isString pattern
+        inspected = try pattern.unescape()?.underline
+        inspected = pattern unless _.isString inspected
+        source = not _.isEmpty try pattern.source or null
+        notReg = "the #{inspected} is not a valid regexp"
+        assert _.isRegExp(pattern) and source or 0, notReg
+        assert @resources = (@resources or []).concat pattern
+        assert @resources = _.unique this.resources or []
+        logger.debug associate, inspected, identify
+        return this # return itself for chaining...
+
+    # This is a very basic method that adds the specified regular
+    # expression pattern to the list of permitted domain patterns.
+    # The patterns are associated with a service class, not object.
+    # Supports implicit extraction of captured groups in the match.
+    # Use this to configure what domains should match with service.
+    @domain: (pattern) ->
+        associate = "Associating %s domain with %s"
+        identify = @identify().underline.toString()
+        r = (s) -> new RegExp "^#{RegExp.escape(s)}$"
+        pattern = try r pattern if _.isString pattern
+        inspected = try pattern.unescape()?.underline
+        inspected = pattern unless _.isString inspected
+        source = not _.isEmpty try pattern.source or null
+        notReg = "the #{inspected} is not a valid regexp"
+        assert _.isRegExp(pattern) and source or 0, notReg
+        assert @domains = (@domains or []).concat pattern
+        assert @domains = _.unique this.domains or []
+        logger.debug associate, inspected, identify
+        return this # return itself for chaining...


hot fix trigger event view

hot fix trigger event view

--- src/hipbone/view.coffee
+++ src/hipbone/view.coffee
@@ -116,8 +116,8 @@
   disable: (selector) ->
     @$(selector).attr('disabled', true)

-  trigger: (name) ->
-    @$el?.trigger("#{name}.view")
+  trigger: (name, args...) ->
+    @$el?.trigger("#{name}.view", args)
     super

   clear: ->


Add Ubuntu 12.04

Add Ubuntu 12.04

--- app/assets/javascripts/angular/controllers/configuration_controller.js.coffee
+++ app/assets/javascripts/angular/controllers/configuration_controller.js.coffee
@@ -9,7 +9,10 @@
         { name: 'citext' },
         { name: 'postgis' } ]
     @$.osList = [
-      { box: 'ubuntu/trusty64', name: 'Ubuntu Trusty 14.04' }
+      { box: 'ubuntu/precise32', name: 'Ubuntu Precise Pangolin 12.04 LTS 64' },
+      { box: 'ubuntu/precise64', name: 'Ubuntu Precise Pangolin 12.04 LTS 64' },
+      { box: 'ubuntu/trusty32', name: 'Ubuntu Trusty 14.04 LTS 32' },
+      { box: 'ubuntu/trusty64', name: 'Ubuntu Trusty 14.04 LTS 64' }
     ]
     @$.coresList = [ '1', '2', '3', '4' ]
     @$.rubyInstalls =


Changed value of RequireStatementInCodeBlock

--- src/rules/RequireStatementInCodeBock.coffee
+++ src/rules/RequireStatementInCodeBock.coffee
@@ -2,7 +2,7 @@

 class RequireStatementInCodeBock extends Rule
     name: 'Require Statement in Code Block'
-    level: 'warning'
+    level: 'error'
     description: """
     All code in a code block must be a valid statement.


change 'less than or equal to' to 'less than'

--- tests/coffee/ezBreadcrumbs-test.coffee
+++ tests/coffee/ezBreadcrumbs-test.coffee
@@ -43,7 +43,7 @@
             console.log "Test Element Width: #{ testElement.width() }. Element should wrap/unwrap on #{ maxWidth }"

             test "check breadcrumb status on width #{ testElement.width() }", ->
-                if testElement.width() <= maxWidth
+                if testElement.width() < maxWidth
                     console.log "testing on window size less than or equal to #{ maxWidth }."

                     equal testElementData.getState(), 'compressed'


allow `parser` as an option to avoid an XHR when data is already known

--- lib/assets/javascripts/jax/builtin/meshes/ply.js.coffee
+++ lib/assets/javascripts/jax/builtin/meshes/ply.js.coffee
@@ -8,27 +8,23 @@
     new Jax.Mesh.PLY "/path/to/model.ply"
     new Jax.Mesh.PLY path: "/path/to/model.ply", method: "POST"
   ###
-  constructor: (options) ->
+  constructor: (options = {}) ->
     if typeof options is 'string' then options = path: options
     @size = 1
-    @path = options?.path
-    @method = options?.method || "GET"
-    delete options.path if options?.path
-    delete options.method if options?.method
+    @method = "GET"
     super options
-
-    xhr = new XMLHttpRequest()
-    self = this
-    # xhr.overrideMimeType "text/plain; charset=x-user-defined"
-    xhr.responseType = "arraybuffer"
-    xhr.onreadystatechange = ->
-      if xhr.readyState is xhr.DONE
-        if xhr.status is 200
-          self.parser = new Jax.Mesh.PLY.Parser xhr.response
-          self.rebuild()
-        else throw new Error "Request for #{self.path} returned status #{xhr.status}"
-    xhr.open @method, @path
-    xhr.send()
+    unless @parser
+      xhr = new XMLHttpRequest()
+      # xhr.overrideMimeType "text/plain; charset=x-user-defined"
+      xhr.responseType = "arraybuffer"
+      xhr.onreadystatechange = =>
+        if xhr.readyState is xhr.DONE
+          if xhr.status is 200
+            @parser = new Jax.Mesh.PLY.Parser xhr.response
+            @rebuild()
+          else throw new Error "Request for #{@path} returned status #{xhr.status}"
+      xhr.open @method, @path
+      xhr.send()

   init: (vertices, colors, textures, normals, indices) ->
     return unless @parser
@@ -63,7 +59,3 @@
           colors[index*4+2] *= intensity
           colors[index*4+3] *= intensity
     null
-
-  render: (args...) ->
-    return unless @parser
-    super args...


Fix docs

Fix docs

--- src/gral/stories.coffee
+++ src/gral/stories.coffee
@@ -157,7 +157,7 @@
 #   - `fStory: boolean`
 #   - `fServer: boolean`
 #   - `storyId: string`
-#   - `t: string` (for stories, creation time)
+#   - `t: number` [ms] (for stories, creation time)
 #   - `src: string?`
 #   - `level: number`
 # * Only for stories:


Correctly showing the target div when hideOthers: true

--- src/imageToggler.coffee
+++ src/imageToggler.coffee
@@ -48,9 +48,8 @@
         quote = $link.data "quote"
         $quoteTarget.text quote
         $div = $target.closest("div")
-        $div.parent().children("div").removeClass "#{@options.dimmerClass} #{@options.activeClass}"
-        $div.addClass @options.activeClass
         if @options.hideOthers
+          $(quoteTargetSelector).show()
           $div.siblings("div").each (index, sibling) ->
             elem = $(sibling).find("[data-quote-target]")
             quoteTargetSelector = elem.attr("data-quote-target")
@@ -58,6 +57,8 @@
             quoteTarget.hide()
           $div.show()
         else
+          $div.parent().children("div").removeClass "#{@options.dimmerClass} #{@options.activeClass}"
+          $div.addClass @options.activeClass
           $div.siblings("div").addClass @options.dimmerClass
         return
       return @ # because it's chainable.


Use  to inject mock service

Use  to inject mock service

--- test/spec/controllers/contact.coffee
+++ test/spec/controllers/contact.coffee
@@ -5,18 +5,27 @@
   # load the controller's module
   beforeEach module 'expressoApp'

+  beforeEach ->
+    AuthServiceMock = {
+      isAuthenticated: ->
+      ,
+      isAuthorized: ->
+    }
+    module ($provide) ->
+      $provide.value 'AuthService', AuthServiceMock
+      null
+
   ContactCtrl = {}

   scope = {}

   # Initialize the controller and a mock scope
   beforeEach inject ($controller, $rootScope, $injector) ->
+    #AuthService = $injector.get('AuthService')
     scope = $rootScope.$new()
-    AuthService = $injector.get('AuthService')
     ContactCtrl = $controller('ContactCtrl', {
       # mocked dependencies
-      $scope: scope,
-      auth: AuthService
+      $scope: scope
     })

   it 'should attach a list of awesomeThings to the scope', ->


Rewritten "if" as guard

Rewritten "if" as guard

--- js-library/app/js/util/scrollto.coffee
+++ js-library/app/js/util/scrollto.coffee
@@ -7,4 +7,6 @@
         axis: 'y'

 Factlink.on 'factlink.factsLoaded', ->
-  scrollTo FactlinkConfig.scrollto if FactlinkConfig?.scrollto?
+  return unless FactlinkConfig.scrollto?
+
+  scrollTo FactlinkConfig.scrollto


cleanup old FB header code

cleanup old FB header code

--- jsapp/xlform/src/view.surveyApp.coffee
+++ jsapp/xlform/src/view.surveyApp.coffee
@@ -52,7 +52,6 @@
       "click #xlf-download": "downloadButtonClick"
       "click #save": "saveButtonClick"
       "click #publish": "publishButtonClick"
-      "click #settings": "toggleSurveySettings"
       "update-sort": "updateSort"
       "click .js-select-row": "selectRow"
       "click .js-select-row--force": "forceSelectRow"
@@ -233,28 +232,6 @@

     getApp: -> @

-    toggleSurveySettings: (evt) ->
-      $et = $(evt.currentTarget)
-      $et.toggleClass('active__settings')
-      if @features.surveySettings
-        $settings = @$(".form__settings")
-        $settings.toggle()
-        close_settings = (e) ->
-          $settings_toggle = $('#settings')
-
-          is_in_settings = (element) ->
-            element == $settings[0] || $settings.find(element).length > 0
-          is_in_settings_toggle = (element) ->
-            element == $settings_toggle[0] || $settings_toggle.find(element).length > 0
-
-          if !(is_in_settings(e.target) || is_in_settings_toggle(e.target))
-            $settings.hide()
-            $et.removeClass('active__settings')
-            $('body').off 'click', close_settings
-
-        $('body').on 'click', close_settings
-
-
     _getViewForTarget: (evt)->
       $et = $(evt.currentTarget)
       modelId = $et.closest('.survey__row').data('row-id')
@@ -330,12 +307,6 @@
         for key, value of validations
             break

-      if @features.displayTitle
-        $viewUtils.makeEditable @, @survey.settings, '.form-title', property:'form_title', options: validate: (value) ->
-          if value.length > 255
-            return "Length cannot exceed 255 characters, is " + value.length + " characters."
-          return
-
       $inps = {}
       _settings = @survey.settings

@@ -376,12 +347,6 @@
       @null_top_row_view_selector = new $viewRowSelector.RowSelector(el: @$el.find(".survey__row__spacer").get(0), survey: @survey, ngScope: @ngScope, surveyView: @, reversible:true)

     _render_hideConditionallyDisplayedContent: ->
-      if not @features.displayTitle
-        @$(".survey-header__inner").hide()
-
-      if not @features.surveySettings
-        @$(".survey-header__options-toggle").hide()
-
       if !@features.multipleQuestions
         @$el.addClass('survey-editor--singlequestion')
         @$el.find(".survey-editor__null-top-row").addClass("survey-editor__null-top-row--hidden")
@@ -750,17 +715,13 @@
     features:
       multipleQuestions: true
       skipLogic: true
-      displayTitle: true
       copyToLibrary: true
-      surveySettings: true

   class surveyApp.QuestionApp extends SurveyFragmentApp
     features:
       multipleQuestions: false
       skipLogic: false
-      displayTitle: false
       copyToLibrary: false
-      surveySettings: false
     render: () ->
       super
       @$('.survey-editor.form-editor-wrap.container').append $('.question__tags')


redir fix for route map

redir fix for route map

--- js/views/route-map.coffee
+++ js/views/route-map.coffee
@@ -17,12 +17,10 @@
       if(utils.locations == null)
         utils.locations = new Locations().fetch()

-      if(utils.route == null || utils.locations == null)
+      if(_.isUndefined(utils.route) || _.isUndefined(utils.locations))
         $.mobile.changePage($("#destination"))
-        console.log "lol"
-
-      console.log utils.route
-      console.log utils.locations
+        utils.app.navigate("#destination", true, true)
+      return

     render: ->


Clean up angle recalculation

Clean up angle recalculation

--- lib/autoprefixer/hacks/gradient.coffee
+++ lib/autoprefixer/hacks/gradient.coffee
@@ -56,9 +56,15 @@
       @directions[value.toLowerCase()] || value
     param.join(' ')

+  # Round float and save digits under dot
+  roundFloat: (float, digits) ->
+    parseFloat(float.toFixed(digits))
+
   # Add 90 degrees
   fixAngle: (param) ->
-    param = parseFloat((Math.abs(450 - parseFloat(param)) % 360).toFixed(3))
+    param = parseFloat(param)
+    param = Math.abs(450 - param) % 360
+    param = @roundFloat(param, 3)
     "#{param}deg"

   # Remove old WebKit gradient too


remove unused allowEOL too

remove unused allowEOL too

--- lib/operators/general-operators.coffee
+++ lib/operators/general-operators.coffee
@@ -73,11 +73,8 @@
 #
 class Delete extends Operator
   register: null
-  allowEOL: null
-
-  # allowEOL - Determines whether the cursor should be allowed to rest on the
-  #            end of line character or not.
-  constructor: (@editor, @vimState, {@allowEOL}={}) ->
+
+  constructor: (@editor, @vimState) ->
     @complete = false
     @register = settings.defaultRegister()

@@ -172,7 +169,7 @@
 class Yank extends Operator
   register: null

-  constructor: (@editor, @vimState, {@allowEOL}={}) ->
+  constructor: (@editor, @vimState) ->
     @register = settings.defaultRegister()

   # Public: Copies the text selected by the given motion.


Added redirection of console output so that the REPL will show println etc output.

--- lib/process/self-hosted-process.coffee
+++ lib/process/self-hosted-process.coffee
@@ -17,17 +17,19 @@
     "SelfHosted"

   start: ({messageHandler, startCallback})->
-    # :) We're always started
+    return if @running()
     @messageHandler = messageHandler
+    @startRedirectingConsoleOutput()
+    @appendText("Self Hosted REPL Started")
     startCallback()

   # TODO displayInRepl doesn't really make sense as an argument.
   # Need to think through what this is really doing and what really makes sense
   # As an argument to the connection.
   sendCommand: (code, displayInRepl, resultHandler)->
-
-    #TODO use this approach to capture console log output and pass as msg.out
-    # http://stackoverflow.com/questions/9277780/can-i-extend-the-console-object-for-rerouting-the-logging-in-javascript
+    # TODO beef up error responses. It currently returns
+    # TypeError: Cannot read property 'call' of undefined at eval
+    # if somewhere within the code you refer to a function that's not defined.

     allowUnsafeEval =>
       allowUnsafeNewFunction =>
@@ -41,21 +43,50 @@
             resultHandler error: error
             @messageHandler err: error

-    # message handler should be passed nrepl like maps
-    #  msg.out or
-    #  msg.err or
-    # msg.ns and msg.value
-    # resultHandler should be passed a map with value or error
-
   interrupt: ->
     # doesn't do anything
     return null

   running: ()->
-    # always running
-    true
+    @messageHandler?

   # Closes the remote connection.
   stop: (session)->
-    # doesn't do anything
-    return null
+    return unless @running()
+    @stopRedirectingConsoleOutput()
+    @appendText("Self hosted REPL stopped")
+
+  # Redirects console.log and friends to the Proto REPL repl.
+  startRedirectingConsoleOutput: ->
+    if @originalLog
+      console.log("Already redirecting logging")
+      return
+    else
+      originalLog = console.log
+      @originalLog = originalLog
+      originalWarn = console.warn
+      @originalWarn = originalWarn
+      originalError = console.error
+      @originalError = originalError
+
+      protoLog = @appendText
+
+      console.log = ->
+        args = Array.prototype.slice.call(arguments)
+        protoLog(args.join(" "))
+        originalLog.apply console, arguments
+      console.warn = ->
+        args = Array.prototype.slice.call(arguments)
+        protoLog(args.join(" "))
+        originalWarn.apply console, arguments
+      console.error = ->
+        args = Array.prototype.slice.call(arguments)
+        protoLog(args.join(" "))
+        originalError.apply console, arguments
+
+  # Stops redirecting console.log and friends to the Proto REPL repl.
+  stopRedirectingConsoleOutput: ->
+    return unless @originalLog
+    console.log = @originalLog
+    console.warn = @originalWarn
+    console.error = @originalError


handle no HUBOT_BASE_URL env var

handle no HUBOT_BASE_URL env var

--- src/join-request.coffee
+++ src/join-request.coffee
@@ -12,6 +12,7 @@
   app = robot.router
   env = process.env
   team = env.HUBOT_SLACK_TEAM or ''
+  url = env.HUBOT_BASE_URL or 'http://please-set-HUBOT_BASE_URL/'

   robot.brain.on 'loaded', ->
     robot.brain.data.ingressAgents ?= []
@@ -55,7 +56,7 @@
     user = req.session.user
     fileTemp = req.files.screenshot.path
     filename = "images/#{fileTemp.split('/').pop()}." + req.files.screenshot.name.split('.').pop()
-    imageUrl = env.HUBOT_BASE_URL.replace /\/$/, "/#{filename}"
+    imageUrl = url.replace /\/$/, "/#{filename}"

     fs.rename fileTemp, "#{__dirname}/public/#{filename}", (err) ->
       if err?


change win32 EXROOT

change win32 EXROOT

--- app.coffee
+++ app.coffee
@@ -8,10 +8,8 @@
 global.ROOT = __dirname
 global.EXECROOT = path.join(process.execPath, '..')
 global.APPDATA_PATH = path.join(app.getPath('appData'), 'poi')
-if process.platform != 'win32'
-  global.EXROOT = global.APPDATA_PATH
-else
-  global.EXROOT = global.EXECROOT
+global.EXROOT = global.APPDATA_PATH
+
 if process.env.DEBUG?
   global.SERVER_HOSTNAME = '127.0.0.1:17027'
 else


Fix string types for interpolation

Fix string types for interpolation

--- app.coffee
+++ app.coffee
@@ -45,7 +45,7 @@
 # no stacktraces leaked to user
 # but it is printed into console
 app.use (err, req, res, next) ->
-    console.error 'PROTECTED_S3_ERROR Uncaught error "#{err?.message}": ', err
+    console.error "PROTECTED_S3_ERROR Uncaught error '#{err?.message}': ", err
     res.status err.status or 500
     res.render 'error',
         message: err.message,


Show all blog posts by default

Show all blog posts by default

--- app.coffee
+++ app.coffee
@@ -91,7 +91,7 @@

 app.get '/blog/rss', blogRoutes.rssList

-app.get '/blog', authenticate, blogRoutes.viewRecent
+app.get '/blog', authenticate, blogRoutes.viewAll

 app.get '/blog.aspx', authenticate, siteRoutes.legacyUrl
 app.get '/blogrss.aspx', authenticate, siteRoutes.legacyUrl


[fix] form validation for edit modal not working due to incorrect target selectors

--- js/profile.coffee
+++ js/profile.coffee
@@ -20,8 +20,8 @@
   )

   #enable/disable the save changes button
-  $("#modal-edit input[type=text]").keyup( ()->
-    if $("#modal-edit-name").val() != ""
-      $("#modal-edit-btn-save").removeClass("disabled")
-    else $("#modal-edit-btn-save").addClass("disabled")
+  $("#modal-edit-grp input[type=text]").keyup( ()->
+    if $("#modal-edit-grp-name").val() != ""
+      $("#modal-edit-grp-btn-save").removeClass("disabled")
+    else $("#modal-edit-grp-btn-save").addClass("disabled")
   )


Regex auto-detects case-sensitivity

Regex auto-detects case-sensitivity

--- src/js/filter.js.coffee
+++ src/js/filter.js.coffee
@@ -31,8 +31,7 @@
 # Regular expression filters
 class RegexFilter extends Filter
   constructor: (text) ->
-    split_text = text.split("/")
-    @regex = new RegExp(split_text[1], split_text[2])
+    @regex = new RegExp(text, if text is text.toLowerCase() then "i" else "")

 class NameRegexFilter extends RegexFilter
   is_troll: (comment) ->


correct color code categorization

correct color code categorization

--- src/ansi_to_html.coffee
+++ src/ansi_to_html.coffee
@@ -139,9 +139,9 @@
 		else if 4 < code < 7 then 'blink'
 		else if code is 8 then 'hide'
 		else if code is 9 then 'strike'
-		else if 29 < code < 38 or code is 39 or 39 < code < 48 or 89 < code < 98
+		else if 29 < code < 38 or code is 39 or 89 < code < 98
 			'foreground-color'
-		else if code is 49 or 99 < code < 108
+		else if 39 < code < 48 or code is 49 or 99 < code < 108
 			'background-color'
 		else null


Store specificity based on the cloned item instead of the original

--- src/menu-helpers.coffee
+++ src/menu-helpers.coffee
@@ -3,8 +3,8 @@
 ItemSpecificities = new WeakMap

 merge = (menu, item, itemSpecificity=Infinity) ->
+  item = cloneMenuItem(item)
   ItemSpecificities.set(item, itemSpecificity) if itemSpecificity
-  item = cloneMenuItem(item)
   matchingItemIndex = findMatchingItemIndex(menu, item)
   matchingItem = menu[matchingItemIndex] unless matchingItemIndex is - 1