Fix group ordering bug

Fix group ordering bug

--- components/Match.coffee
+++ components/Match.coffee
@@ -4,6 +4,7 @@
   constructor: ->
     @matches = null
     @methods = null
+    @groups = null

     @inPorts =
       in: new noflo.Port 'object'
@@ -21,12 +22,15 @@
       @methods ?= []
       @methods.push method.toLowerCase()

+    @inPorts.in.on 'connect', =>
+      @groups = []
     @inPorts.in.on 'begingroup', (group) =>
-      @outPorts.out.beginGroup group
+      @groups.push group
     @inPorts.in.on 'endgroup', (group) =>
-      @outPorts.out.endGroup group
+      @groups.pop()
     @inPorts.in.on 'disconnect', =>
       @outPorts.out.disconnect()
+      @outPorts.fail.disconnect()

     @inPorts.in.on 'data', (data) =>
       success = true
@@ -46,8 +50,15 @@

       # If all pass, forward to OUT; FAIL otherwise
       if success
-        @outPorts.out.send data
+        @output 'out', data
       else
-        @outPorts.fail.send data
+        @output 'fail', data
+
+  output: (portName, data) ->
+    port = @outPorts[portName]
+
+    port.beginGroup group for group in @groups
+    port.send data
+    port.endGroup() for group in @groups

 exports.getComponent = -> new Match


Environments: MachineItem: Remove itemHeight

--- client/Environments/views/scene/environmentmachinecontainer.coffee
+++ client/Environments/views/scene/environmentmachinecontainer.coffee
@@ -5,7 +5,6 @@
     options.cssClass   = 'machines'
     options.itemClass  = EnvironmentMachineItem
     options.title      = 'Virtual Machines'
-    options.itemHeight = 50

     super options, data


Mouse movement - scalar varable implemented

--- client/mobile/app/coffee/modules/interface/show/views/mouse.coffee
+++ client/mobile/app/coffee/modules/interface/show/views/mouse.coffee
@@ -56,9 +56,10 @@
     # console.log @newPos

     # New mouse position
-    @deltaPos = { x: Math.round(@newPos.x - @oldPos.x), y: Math.round(@newPos.y - @oldPos.y) }
-    @deltaPos.x = 5*@deltaPos.x
-    @deltaPos.y = 5*@deltaPos.y
+    scalar = 5
+    @deltaPos = { x: Math.round(scalar * (@newPos.x - @oldPos.x)), y: Math.round(scalar * (@newPos.y - @oldPos.y)) }
+    @deltaPos.x = @deltaPos.x
+    @deltaPos.y = @deltaPos.y

     # console.log @deltaPos


JS bugfix

JS bugfix

--- common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee
+++ common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee
@@ -153,7 +153,7 @@
     event.preventDefault()
     if @child_state == 'post_assessment'

-      $.postWithPrefix "#{@ajax_url}/skip_post_assessment", data, (response) =>
+      $.postWithPrefix "#{@ajax_url}/skip_post_assessment", {}, (response) =>
         if response.success
           @child_state = 'done'
           @allow_reset = response.allow_reset


Randomise noise and signal waveforms

Randomise noise and signal waveforms

One step closer to full client side implementation :D

--- coffee/game.coffee
+++ coffee/game.coffee
@@ -534,18 +534,18 @@
       mass1: 10
       mass2: 10
       inclination: 90
-    signalSrc: "signalBank/sigbank39/hplus.mp3"
+    signalSrc: ""
     ob1:
-      src: "signalBank/sigbank24/noise.mp3"
+      src: ""
       signal: 0
     ob2:
-      src: "signalBank/sigbank39/data2.mp3"
-      signal: 1
+      src: ""
+      signal: 0
     ob3:
-      src: "signalBank/sigbank50/noise.mp3"
+      src: ""
       signal: 0
     ob4:
-      src: "signalBank/sigbank72/noise.mp3"
+      src: ""
       signal: 0

   computed:
@@ -575,6 +575,25 @@
     massText: () -> @locs[@loc].game.main['mass-text']
     inclinationText: () -> @locs[@loc].game.main['inclination-text']

+  created: () ->
+    # Generate paths to random noise waveforms
+    sigBankIDs = (Math.floor(84*Math.random() + 1) for i in [0...4])
+    for i in [0..3]
+      @["ob#{i+1}"].src = "signalBank/sigbank#{sigBankIDs[i]}/noise.mp3"
+
+    # Choose waveform that will contain a signal
+    sigLoc = Math.floor 4*Math.random()
+    sigID = sigBankIDs[sigLoc]
+    @["ob#{sigLoc+1}"].src = "signalBank/sigbank#{sigID}/data#{@level}.mp3"
+    @["ob#{sigLoc+1}"].signal = 1
+    @signalSrc = "signalBank/sigbank#{sigID}/hplus.mp3"
+
+    # Prepare signal metadata
+    metadata = require "../signalBank.json"
+    @info.mass1 = metadata["info#{sigID}"].mass1
+    @info.mass2 = metadata["info#{sigID}"].mass2
+    @info.inclination = metadata["info#{sigID}"].inclination
+
   mounted: () ->
     setRenderLives()
     addWaveforms()
@@ -585,7 +604,6 @@

   methods:
     play: () ->
-      console.log "hello, world"
       visibleWave().play()

     pause: () ->
@@ -625,7 +643,6 @@

       _.delay( (waveID) ->
         # Check if visible data contains the signal
-        console.log $('#'+waveID).data('signal')
         if $('#'+waveID).data('signal') is 1
           # Show continue
           $('#tick-noclick').hide()


demo works

demo works

--- coffee/main.coffee
+++ coffee/main.coffee
@@ -1,3 +1,14 @@
+class LiterallyCanvas
+
+  constructor: (@canvas) ->
+    @ctx = @canvas.getContext('2d')
+
+  test: ->
+    @ctx.clearRect(0, 0, @canvas.width, @canvas.height)
+
+
 $.fn.literallycanvas = ->
+  lc = new LiterallyCanvas(@find('canvas').get(0))
   @click (e) =>
-    @attr 'background-color', '#f00'
+    @find('canvas').css('background-color', 'red')
+    lc.test()


sneaky capture

sneaky capture

--- coffee/main.coffee
+++ coffee/main.coffee
@@ -1121,6 +1121,7 @@
            @iconCache[name] = tex
            if f?
              f(tex)
+             true
       )
     else
       f(@iconCache[name])
@@ -1579,20 +1580,21 @@
       loadedModels[m.props.name] = m

       if m.props.icon != ''
-        @ve.loadIcon(m.props.name, (tex) =>
-          tex = @ve.iconCache[m.props.name]
-          _tex = tex.clone()
-          _tex.needsUpdate = true
-          m.setIcon(tex)
-          for x in m.instances
-            x.tex = tex.clone()
-            x.tex.needsUpdate = true
-            x.setIcon(
-              x.shp.obj3d.position.x,
-              x.shp.obj3d.position.y
-            )
-        )
-        true
+        do (m) => #capture m-ref before it gets clobbered by loop
+          @ve.loadIcon(m.props.name, (tex) ->
+            #tex = @ve.iconCache[m.props.name]
+            _tex = tex.clone()
+            _tex.needsUpdate = true
+            m.setIcon(_tex)
+            for i in m.instances
+              i.tex = tex.clone()
+              i.tex.needsUpdate = true
+              i.setIcon(
+                i.shp.obj3d.position.x,
+                i.shp.obj3d.position.y
+              )
+          )
+          true

   loadSimSettings: (settings) =>
     @ve.simSettings.props = settings


minor: use === in autogenerated code for rules

--- coffee/rule.coffee
+++ coffee/rule.coffee
@@ -99,7 +99,7 @@

 exports.binaryTransitionFunc2GenericCode = (binTf) ->
-  row2condition = (row) -> ("s==#{sum}" for nextValue, sum in row when nextValue).join(" || ")
+  row2condition = (row) -> ("s===#{sum}" for nextValue, sum in row when nextValue).join(" || ")

   conditionBorn = row2condition binTf.table[0]
   conditionStay = row2condition binTf.table[1]
@@ -116,23 +116,23 @@
     //Transition function. Takes current state and sum, returns new state.
     //this.generation stores current generation number
     'next': function(x, s){
-        if (x==1 && (#{conditionStay})) return 1;
-        if (x==0 && (#{conditionBorn})) return 1;
+        if (x===1 && (#{conditionStay})) return 1;
+        if (x===0 && (#{conditionBorn})) return 1;
         return 0;
      }
 }"""]

 exports.dayNightBinaryTransitionFunc2GenericCode = (binTf) ->
-  row2condition = (row) -> ("s==#{sum}" for nextValue, sum in row when nextValue).join(" || ")
-  row2conditionInv = (row) -> ("s==#{binTf.base.numNeighbors-sum}" for nextValue, sum in row when nextValue).join(" || ")
+  row2condition = (row) -> ("s===#{sum}" for nextValue, sum in row when nextValue).join(" || ")
+  row2conditionInv = (row) -> ("s===#{binTf.base.numNeighbors-sum}" for nextValue, sum in row when nextValue).join(" || ")

   conditionBorn = row2condition binTf.base.table[0]
   conditionStay = row2condition binTf.base.table[1]
   conditionBornInv = row2conditionInv binTf.base.table[0]
   conditionStayInv = row2conditionInv binTf.base.table[1]

-  code = ["""//Automatically generated code for binary rule #{binTf}
+  code = ["""//Automatically generated code for population-inverting binary rule #{binTf}
 {
     //number of states
     'states': 2,
@@ -145,13 +145,19 @@
     'next': function(x, s){
         var phase = this.generation & 1;

+        //The original rule #{binTf} inverts state of an empty field
+        //To calculate it efficiently, we instead invert each odd generation, so that population never goes to infinity.
+
+
         if (phase === 0){
-            if (x==1 && (#{conditionStay})) return 0;
-            if (x==0 && (#{conditionBorn})) return 0;
+            //On even generations, invert output
+            if (x===1 && (#{conditionStay})) return 0;
+            if (x===0 && (#{conditionBorn})) return 0;
             return 1
         } else {
-            if (x==0 && (#{conditionStayInv})) return 1;
-            if (x==1 && (#{conditionBornInv})) return 1;
+            //On odd generations, invert input state and nighbors sum
+            if (x===0 && (#{conditionStayInv})) return 1;
+            if (x===1 && (#{conditionBornInv})) return 1;
             return 0;
         }
      }


Account: unnecessary listcontroller creation avoided

--- client/Account/AppView.coffee
+++ client/Account/AppView.coffee
@@ -45,17 +45,18 @@
     {listType, listHeader} = @getData()

     @addSubView @header = new KDHeaderView type : "medium", title : listHeader
-    type  = if listType then listType or ''
+    type = if listType then listType or ''

     ListView   = if listClasses[type] then listClasses[type] else KDListView
-    Controller = if listClasses["#{type}Controller"]
-    then listClasses["#{type}Controller"]
-    else KDListViewController
+    Controller = if listClasses["#{type}Controller"] then listClasses["#{type}Controller"]

-    controller = new Controller
-      view     : new ListView cssClass : type, delegate: this
+    view = new ListView cssClass : type, delegate: this

-    @addSubView controller.getView()
+    if controller
+      controller = new Controller {view}
+      view       = controller.getView()
+
+    @addSubView view

 class AccountsSwappable extends KDView
   constructor:(options,data)->


Workspace: Created a fallback header.

Workspace: Created a fallback header.

--- client/Workspace/panel.coffee
+++ client/Workspace/panel.coffee
@@ -15,9 +15,12 @@
     {title}         = options
     buttonsLength   = options.buttons?.length

-    @createHeader title     if title or buttonsLength
-    @createHeaderButtons()  if buttonsLength
-    @createHeaderHint()     if options.hint
+    if title or buttonsLength
+      @createHeader title
+      @createHeaderButtons()  if buttonsLength
+      @createHeaderHint()     if options.hint
+    else
+      @header = new KDCustomHTMLView { cssClass: "hidden" }

     @createLayout()


Hotfix: Assets are exposed to processors

--- client/src/app/redWire.coffee
+++ client/src/app/redWire.coffee
@@ -411,7 +411,7 @@
     return result # Quit early

   try
-    result.signal = processor.update(evaluatedPins, constants.transformers, RW.transformersLogger)
+    result.signal = processor.update(evaluatedPins, constants.assets, constants.transformers, RW.transformersLogger)
   catch e
     result.signal = RW.signals.ERROR
     RW.transformersLogger(RW.logLevels.ERROR, "Calling processor #{chip.processor}.update raised an exception #{e}. Input pins were #{JSON.stringify(evaluatedPins)}.\n#{RW.formatStackTrace(e)}")
@@ -766,8 +766,8 @@
   """
   return RW.compileSource(source, evaluator, ["context"])

-# Compile processor.update() source into sandboxed function of (pins, transformers, log)
-RW.compileUpdate = (expressionText, evaluator) -> RW.compileSource(expressionText, evaluator, ["pins", "transformers", "log"])
+# Compile processor.update() source into sandboxed function of (pins, assets, transformers, log,)
+RW.compileUpdate = (expressionText, evaluator) -> RW.compileSource(expressionText, evaluator, ["pins", "assets", "transformers", "log"])

 # Compile processor listActiveChildren source into sandboxed function of (pins, children, transformers, log)
 RW.compileListActiveChildren = (expressionText, evaluator) -> RW.compileSource(expressionText, evaluator, ["pins", "children", "transformers", "log"])


Distinguish between separate percentage rollouts

--- services/web/app/coffee/Features/Project/ProjectController.coffee
+++ services/web/app/coffee/Features/Project/ProjectController.coffee
@@ -24,13 +24,16 @@
 Sources = require "../Authorization/Sources"
 TokenAccessHandler = require '../TokenAccess/TokenAccessHandler'
 CollaboratorsHandler = require '../Collaborators/CollaboratorsHandler'
+crypto = require 'crypto'

 module.exports = ProjectController =

-	_isInPercentageRollout: (objectId, percentage) ->
+	_isInPercentageRollout: (rolloutName, objectId, percentage) ->
 		if Settings.bypassPercentageRollouts = true
 			return true
-		counter = parseInt(objectId.toString().substring(18, 24), 16)
+		data = "#{rolloutName}:#{objectId.toString()}"
+		md5hash = crypto.createHash('md5').update(data).digest('hex')
+		counter = parseInt(md5hash.slice(26, 32), 16)
 		return (counter % 100) < percentage

 	updateProjectSettings: (req, res, next) ->
@@ -274,7 +277,7 @@
 				timestamp = parseInt(user_id.toString().substring(0, 8), 16)

 				rolloutPercentage = 60 # Percentage of users to roll out to
-				if !ProjectController._isInPercentageRollout(user_id, rolloutPercentage)
+				if !ProjectController._isInPercentageRollout('autocompile', user_id, rolloutPercentage)
 					# Don't show if user is not part of roll out
 					return cb(null, { enabled: false, showOnboarding: false })
 				userSignupDate = new Date(timestamp * 1000)
@@ -305,7 +308,11 @@

 			token = TokenAccessHandler.getRequestToken(req, project_id)
 			isTokenMember = results.isTokenMember
-			enableTokenAccessUI = ProjectController._isInPercentageRollout(project.owner_ref, 0)
+			enableTokenAccessUI = ProjectController._isInPercentageRollout(
+				'linksharing',
+				project.owner_ref,
+				0
+			)
 			AuthorizationManager.getPrivilegeLevelForProject user_id, project_id, token, (error, privilegeLevel)->
 				return next(error) if error?
 				if !privilegeLevel? or privilegeLevel == PrivilegeLevels.NONE


updating js spec: cloning product calls insertProductAfter now

--- spec/javascripts/unit/admin/services/bulk_products_spec.js.coffee
+++ spec/javascripts/unit/admin/services/bulk_products_spec.js.coffee
@@ -61,7 +61,7 @@
       clonedProduct =
         id: 17

-      spyOn(BulkProducts, "addProducts")
+      spyOn(BulkProducts, "insertProductAfter")
       BulkProducts.products = [originalProduct]
       $httpBackend.expectGET("/admin/products/oranges/clone.json").respond 200,
         product: clonedProduct


Simplify onSubmit

Simplify onSubmit

--- backend/app/assets/javascripts/spree/backend/stock_management/index_update_forms.coffee
+++ backend/app/assets/javascripts/spree/backend/stock_management/index_update_forms.coffee
@@ -19,8 +19,8 @@
   onEdit: (ev) ->
     ev.preventDefault()
     @$('[name=backorderable]').prop('disabled', false)
+    storeBackorderableState(stockItemId)
     stockItemId = @model.id
-    storeBackorderableState(stockItemId)
     Spree.NumberFieldUpdater.hideReadOnly(stockItemId)
     Spree.NumberFieldUpdater.showForm(stockItemId)

@@ -39,10 +39,8 @@

   onSubmit: (ev) ->
     ev.preventDefault()
-    stockItemId = @model.id
-    stockLocationId = $(ev.currentTarget).data('location-id')
-    backorderable = $("#backorderable-#{stockItemId}").prop("checked")
-    countOnHand = parseInt($("#number-update-#{stockItemId} input[type='number']").val(), 10)
+    backorderable = @$('[name=backorderable]').prop("checked")
+    countOnHand = parseInt($("input[name='count_on_hand']").val(), 10)

     @model.set
       count_on_hand: countOnHand


added german interface translations

added german interface translations

--- coffeescripts/cards-de.coffee
+++ coffeescripts/cards-de.coffee
@@ -72,33 +72,33 @@
         '.xwing-card-browser option[value="type-by-points"]': 'Typ (Punkte)'
         '.xwing-card-browser option[value="type-by-name"]': 'Typ (Name)'
         '.xwing-card-browser .translate.select-a-card': 'Wähle eine Karte aus der Liste.'
-        '.xwing-card-browser .info-range td': 'Range'
+        '.xwing-card-browser .info-range td': 'Reichweite'
         # Info well
-        '.info-well .info-ship td.info-header': 'Ship'
+        '.info-well .info-ship td.info-header': 'Schiff'
         '.info-well .info-skill td.info-header': 'Skill'
-        '.info-well .info-actions td.info-header': 'Actions'
+        '.info-well .info-actions td.info-header': 'Aktionen'
         '.info-well .info-upgrades td.info-header': 'Upgrades'
-        '.info-well .info-range td.info-header': 'Range'
+        '.info-well .info-range td.info-header': 'Reichweite'
         # Squadron edit buttons
-        '.clear-squad' : 'New Squad'
-        '.save-list' : 'Save'
-        '.save-list-as' : 'Save as…'
-        '.delete-list' : 'Delete'
-        '.backend-list-my-squads' : 'Load squad'
-        '.view-as-text' : ' Print/View as Text'
-        '.randomize' : 'Random!'
-        '.randomize-options' : 'Randomizer options…'
+        '.clear-squad' : 'Neues Squad'
+        '.save-list' : 'Speichern'
+        '.save-list-as' : 'Speichern als…'
+        '.delete-list' : 'Löschen'
+        '.backend-list-my-squads' : 'Squad laden'
+        '.view-as-text' : ' Drucken/Anzeigen als Text'
+        '.randomize' : 'Zufallsliste!'
+        '.randomize-options' : 'Zufallslistenoptionen…'
         # Print/View modal
-        '.bbcode-list' : 'Copy the BBCode below and paste it into your forum post.<textarea></textarea>'
-        '.vertical-space-checkbox' : """<input type="checkbox" class="toggle-vertical-space" /> Add space for damage/upgrade cards when printing"""
+        '.bbcode-list' : 'Kopiere den BBCode von unten und füge ihn in deine Forenposts ein.<textarea></textarea>'
+        '.vertical-space-checkbox' : """<input type="checkbox" class="toggle-vertical-space" /> Platz für Schadenskarten und Upgrades im Druck berücksichtigen."""
         '.print-list' : ' Print'
         # Randomizer options
-        '.do-randomize' : 'Randomize!'
+        '.do-randomize' : 'Zufall!'
         # Top tab bar
-        '#empireTab' : 'Galactic Empire'
-        '#rebelTab' : 'Rebel Alliance'
-        '#browserTab' : 'Card Browser'
-        '#aboutTab' : 'About'
+        '#empireTab' : 'Galaktisches Imperium'
+        '#rebelTab' : 'Rebellen Allianz'
+        '#browserTab' : 'Karten Browser'
+        '#aboutTab' : 'Über'
     singular:
         'pilots': 'Piloten'
         'modifications': 'Modifikationen'


Fix missing ship assignments.

Fix missing ship assignments.

--- coffeescripts/cards-de.coffee
+++ coffeescripts/cards-de.coffee
@@ -483,14 +483,17 @@
         "Raider-class Corvette (Aft)":
             name: "Korv. der Sturm-Klasse (Heck)"
         "Talonbane Cobra":
+            ship: "Kihraxz-Jäger"
             text: """Sobald du angreifst oder verteidigst, wird der Effekt deiner Kampfvorteile durch Reichweite verdoppelt."""
         "Miranda Doni":
             text: """Ein Mal pro Runde darfst du, sobald du angreifst, entweder 1 Schild ausgeben, um 1 zusätzlichen Angriffswürfel zu werfen, <strong>oder<strong> 1 Angriffswürfel weniger werfen, um 1 Schild wiederaufzuladen."""
         '"Redline"':
             name: '"Rote Linie"'
+            ship: "TIE-Vergelter"
             text: """Du darfst 2 Zielerfassungen auf demselben Schiff haben. Sobald du ein Schiff in die Zielerfassung nimmst, darfst du es ein zweites Mal in die Zielerfassung nehmen."""
         '"Deathrain"':
             name: '"Todesregen"'
+            ship: "TIE-Vergelter"
             text: """Sobald du eine Bombe legst, darfst du die Stopper am Bug deines Schiffs benutzen. Nachdem du eine Bombe gelegt hast, darfst du als freie Aktion eine Fassrolle durchführen."""
         "Juno Eclipse":
             text: """Sobald du dein Manöver aufdeckst, darfst du die Geschwindigkeit um 1 erhöhen oder reduzieren (bis zu einem Minimum von 1)."""
@@ -501,6 +504,7 @@
         "Latts Razzi":
             text: """Sobald ein freundliches Schiff einen Angriff deklariert und du den Verteidiger in der Zielerfassung hast, kannst du einen Zielerfassungsmarker ausgeben, um die Wendigkeit des Verteidigers für diesen Angriff um 1 zu senken."""
         "Graz the Hunter":
+            ship: "Kihraxz-Jäger"
             name: "Graz der Jäger"
             text: """Wirf 1 zusätzlichen Verteidigungswürfel, wenn der Angreifer in deinem Feuerwinkel ist, sobald du verteidigst."""
         "Esege Tuketu":


Re-add (English) text for TBC #385

Re-add (English) text for TBC #385

--- coffeescripts/cards-pl.coffee
+++ coffeescripts/cards-pl.coffee
@@ -658,6 +658,8 @@
             text: '''When you reveal a reverse maneuver, you may drop a bomb using your front guides (including a bomb with the "Action:" header).'''
         'Sarco Plank':
             text: '''When defending, instead of using your agility value, you may roll a number of defense dice equal to the speed of the maneuver you executed this round.'''
+        "Talonbane Cobra":
+            text: """When attacking or defending, double the effect of your range combat bonuses."""

     upgrade_translations =
         #Elitarna zdolność


fix comment

fix comment

--- pvl-tools/pvl.cson
+++ pvl-tools/pvl.cson
@@ -100,8 +100,7 @@
     # This query operates on the newly extractaparsed comment, not the original page load
     # This is the selector for the post attachment links, which contains the
     # proof text. It's the second <a> tag inside the div that's the immediate
-    # *sibling* of the 'userContet' div. The second of these three <a> tags
-    # contains the proof text, the others are blank. We check only the second.
+    # *sibling* of the 'userContet' div. That <a> tags contains the proof text.
     { selector_css: {
       , selectors: ["div.userContent+div a", 1]
       , into: "link_text"


update asana help message

update asana help message

--- scripts/asana-info.coffee
+++ scripts/asana-info.coffee
@@ -13,7 +13,7 @@
 #   HUBOT_ASANA_API_KEY - find this in Account Settings -> API
 #
 # Commands:
-#   post Asana task URL - get info about task
+#   <Asana task URL> - get info about task
 #
 # Author:
 #   mxlje


Use path and context to retrieve the directory

--- src/beautifiers/prettier.coffee
+++ src/beautifiers/prettier.coffee
@@ -2,6 +2,7 @@

 Beautifier = require('./beautifier')
 prettier = require("prettier")
+path = require("path")

 module.exports = class Prettier extends Beautifier
   name: "Prettier"
@@ -23,7 +24,7 @@
     Markdown: false
   }

-  beautify: (text, language, options) ->
+  beautify: (text, language, options, context) ->
     return new @Promise((resolve, reject) ->
       _ = require('lodash')

@@ -33,7 +34,7 @@
       else
         reject(new Error("Unknown language for Prettier"))

-      filePath = atom.workspace.getActiveTextEditor().getPath()
+      filePath = context.filePath and path.dirname context.filePath

       try
         prettier.resolveConfig(filePath).then((configOptions) ->


rate limit on reconnection attempts (cursor/click)

--- services/web/public/coffee/ide/connection/ConnectionManager.coffee
+++ services/web/public/coffee/ide/connection/ConnectionManager.coffee
@@ -36,16 +36,19 @@
 				inactive_disconnect: false

 			@$scope.tryReconnectNow = () =>
-				@tryReconnect()
+				# user manually requested reconnection via "Try now" button
+				@tryReconnectWithRateLimit({force:true})

 			@$scope.$on 'cursor:editor:update', () =>
 				@lastUserAction = new Date()  # time of last edit
 				if !@connected
-					@tryReconnect()
+					# user is editing, try to reconnect
+					@tryReconnectWithRateLimit()

 			document.querySelector('body').addEventListener 'click', (e) =>
 				if !@connected and e.target.id != 'try-reconnect-now-button'
-					@tryReconnect()
+					# user is editing, try to reconnect
+					@tryReconnectWithRateLimit()

 			@ide.socket = io.connect null,
 				reconnect: false
@@ -210,7 +213,24 @@
 			# use socket.io connect() here to make a single attempt, the
 			# reconnect() method makes multiple attempts
 			@ide.socket.socket.connect()
+			# record the time of the last attempt to connect
+			@lastConnectionAttempt = new Date()
 			setTimeout (=> @startAutoReconnectCountdown() if !@connected), 2000
+
+		MIN_RETRY_INTERVAL: 1000 # ms
+		BACKGROUND_RETRY_INTERVAL : 30 * 1000 # ms
+
+		tryReconnectWithRateLimit: (options) ->
+			# bail out if the reconnect is already in progress
+			return if @$scope.connection?.reconnecting
+			# bail out if we are going to reconnect soon anyway
+			reconnectingSoon = @$scope.connection?.reconnection_countdown? and @$scope.connection.reconnection_countdown <= 5
+			clickedTryNow = options?.force  # user requested reconnection
+			return if reconnectingSoon and not clickedTryNow
+			# bail out if we tried reconnecting recently
+			allowedInterval = if clickedTryNow then @MIN_RETRY_INTERVAL else @BACKGROUND_RETRY_INTERVAL
+			return if @lastConnectionAttempt? and new Date() - @lastConnectionAttempt < allowedInterval
+			@tryReconnect()

 		disconnectIfInactive: ()->
 			@userIsInactive = (new Date() - @lastUserAction) > @disconnectAfterMs


clear the pdf renderqueue faster after the initial page has rendered

--- services/web/public/coffee/ide/pdfng/directives/pdfRenderer.coffee
+++ services/web/public/coffee/ide/pdfng/directives/pdfRenderer.coffee
@@ -6,7 +6,7 @@
 	App.factory 'PDFRenderer', ['$q', '$timeout', 'pdfAnnotations', 'pdfTextLayer', ($q, $timeout, pdfAnnotations, pdfTextLayer) ->

 		class PDFRenderer
-			@JOB_QUEUE_INTERVAL: 100
+			JOB_QUEUE_INTERVAL: 25

 			constructor: (@url, @options) ->
 				PDFJS.disableAutoFetch = true
@@ -61,16 +61,16 @@
 					q.pagenum != pagenum
 				# @stopSpinner (element.canvas)

-			triggerRenderQueue: () ->
+			triggerRenderQueue: (interval = @JOB_QUEUE_INTERVAL) ->
 				$timeout () =>
 					@processRenderQueue()
-				, @JOB_QUEUE_INTERVAL
+				, interval

 			removeCompletedJob: (pagenum) ->
 				# may need to clean up deferred object here
 				delete @renderTask[pagenum]
 				@jobs = @jobs - 1
-				@triggerRenderQueue()
+				@triggerRenderQueue(0)

 			renderPage: (element, pagenum) ->
 				viewport = $q.defer()


fix tests

fix tests

--- services/web/test/UnitTests/coffee/Compile/ClsiManagerTests.coffee
+++ services/web/test/UnitTests/coffee/Compile/ClsiManagerTests.coffee
@@ -174,6 +174,7 @@
 							timeout : 100
 							imageName: @image
 							draft: false
+							check: undefined
 						rootResourcePath: "main.tex"
 						resources: [{
 							path:    "main.tex"


Added unsolved problems to visualization

--- picoCTF-web/web/coffee/classroom.coffee
+++ picoCTF-web/web/coffee/classroom.coffee
@@ -26,15 +26,26 @@
       apiCall "GET", "/api/team/stats/solved_problems", {tid: tid}
       .done (data) ->
         teamData = data.data
-        users = ["users"].concat _.keys(teamData.members), [{role: 'annotation'}]
+        users = ["users"].concat _.keys(teamData.members), "Unsolved", [{role: 'annotation'}]

         graphData = [users]
         _.each teamData.problems, (problems, category) ->
+
           categoryData = [category]
+          solvedSet = []
+
           _.each teamData.members, (solved, member) ->
-            categoryData.push _.intersection(solved, problems).length
+
+            userSolved = _.intersection solved, problems
+            categoryData.push userSolved.length
+
+            solvedSet = _.union solvedSet, userSolved
+
+          #Number of unsolved problems
+          categoryData.push _.difference(problems, solvedSet).length
           categoryData.push ''
+
           graphData.push categoryData

         packagedData = google.visualization.arrayToDataTable graphData


Fix bug which prevented the maximum title length from being enforced on input [#144047783]

--- src/code/views/node-view.coffee
+++ src/code/views/node-view.coffee
@@ -77,7 +77,7 @@
       onKeyUp: if canDeleteWhenEmpty then @detectDeleteWhenEmpty else null
       onChange: @updateTitle
       defaultValue: displayTitle
-      maxLength: @maxTitleLength
+      maxLength: @maxTitleLength()
       placeholder: @titlePlaceholder()
       onBlur: =>
         @finishEditing()


Stop double-click on a node to open the relation window in diagram-only mode

--- src/code/views/node-view.coffee
+++ src/code/views/node-view.coffee
@@ -1,6 +1,7 @@
 {input, div, i, img, span, label, img} = React.DOM
 tr = require "../utils/translate"

+AppSettingsStore    = require "../stores/app-settings-store"
 SimulationActions = require("../stores/simulation-store").actions
 SquareImage = React.createFactory require "./square-image-view"
 StackedImage = React.createFactory require "./stacked-image-view"
@@ -125,7 +126,9 @@
     if @props.data.inLinks().length > 0
       now = (new Date()).getTime()
       if now - (@lastClickLinkTime || 0) <= 250
-        InspectorPanelStore.actions.openInspectorPanel 'relations'
+        # Only open inspector if we're not in diagram-only mode
+        if AppSettingsStore.store.settings.simulationType != 0
+          InspectorPanelStore.actions.openInspectorPanel 'relations'
       @lastClickLinkTime = now

   propTypes:


refactor(constants): use openfairDB category IDs

--- src/constants/Categories.coffee
+++ src/constants/Categories.coffee
@@ -3,10 +3,9 @@
 c = {}

 c.IDS =
-
-  INITIATIVE : "1"
-  EVENT      : "2"
-  COMPANY    : "3"
+  INITIATIVE : "2cd00bebec0c48ba9db761da48678134"
+  EVENT      : "c2dc278a2d6a4b9b8a50cb606fc017ed"
+  COMPANY    : "77b3c33a92554bcf8e8c2c86cedd6f6f"

 c.MAIN_IDS = [c.IDS.INITIATIVE, c.IDS.EVENT, c.IDS.COMPANY]


Deprecate old style calls to ContextMenuManager::add

--- src/context-menu-manager.coffee
+++ src/context-menu-manager.coffee
@@ -6,6 +6,7 @@
 fs = require 'fs-plus'
 {specificity} = require 'clear-cut'
 {Disposable} = require 'event-kit'
+Grim = require 'grim'
 MenuHelpers = require './menu-helpers'

 SpecificityCache = {}
@@ -85,6 +86,7 @@
   #     * `event` The click event that deployed the context menu.
   add: (items) ->
     unless typeof arguments[0] is 'object'
+      Grim.deprecate("ContextMenuManage::add has changed to take a single object as its argument. Please consult the documentation.")
       legacyItems = arguments[1]
       devMode = arguments[2]?.devMode
       return @add(@convertLegacyItems(legacyItems, devMode))


`allSaved()` method from LineItems needs to be spy

--- spec/javascripts/unit/admin/line_items/controllers/line_items_controller_spec.js.coffee
+++ spec/javascripts/unit/admin/line_items/controllers/line_items_controller_spec.js.coffee
@@ -47,6 +47,7 @@
       delete: (lineItem, callback=null) ->
         callback() if callback
         return Promise.resolve()
+      allSaved: jasmine.createSpy('allSaved').and.returnValue(true)

     httpBackend.expectGET("/api/v0/orders.json?q%5Bcompleted_at_gteq%5D=SomeDate&q%5Bcompleted_at_lt%5D=SomeDate&q%5Bcompleted_at_not_null%5D=true&q%5Bdistributor_id_eq%5D=&q%5Border_cycle_id_eq%5D=&q%5Bshipment_state_not_eq%5D=shipped&q%5Bstate_not_eq%5D=canceled").respond {orders: [order], pagination: {page: 1, pages: 1, results: 1}}
     httpBackend.expectGET("/admin/enterprises/visible.json?ams_prefix=basic&q%5Bsells_in%5D%5B%5D=own&q%5Bsells_in%5D%5B%5D=any").respond [distributor]


Fix log

Fix log

--- views/app.cjsx
+++ views/app.cjsx
@@ -83,7 +83,6 @@
             else
               <TabPane key={components.length + 1 + index} eventKey={components.length - 1 + (counter += 1)} tab={plugin.displayName} id={plugin.name} className='poi-app-tabpane'>
               {
-                console.log "#{plugin.name} #{components.length - 1 + counter}"
                 React.createElement plugin.reactClass
               }
               </TabPane>


put map type controls back to their default position since the top right corner is available again

--- web/js/views/map_view.coffee
+++ web/js/views/map_view.coffee
@@ -19,10 +19,8 @@
       @map = new google.maps.Map(@el,
         zoom: 16
         mapTypeId: google.maps.MapTypeId.ROADMAP
-        mapTypeControlOptions:
-          position: google.maps.ControlPosition.TOP_CENTER
-        minZoom: 10,
-        maxZoom: 18,
+        minZoom: 10
+        maxZoom: 18
         scaleControl: true
         styles: [
           {


assign options later after locale is set.

--- resources/assets/lib/report-form.coffee
+++ resources/assets/lib/report-form.coffee
@@ -21,24 +21,25 @@
 import { SelectOptions } from 'select-options'

 bn = 'report-form'
-options = [
-  { id: 'Cheating', text: osu.trans 'users.report.options.cheating' },
-  { id: 'Insults', text: osu.trans 'users.report.options.insults' },
-  { id: 'Spam', text: osu.trans 'users.report.options.spam' },
-  { id: 'UnwantedContent', text: osu.trans 'users.report.options.unwanted_content' },
-  { id: 'Nonsense', text: osu.trans 'users.report.options.nonsense' },
-  { id: 'Other', text: osu.trans 'users.report.options.other' },
-]

 export class ReportForm extends PureComponent
   constructor: (props) ->
     super props

+    @options = [
+      { id: 'Cheating', text: osu.trans 'users.report.options.cheating' },
+      { id: 'Insults', text: osu.trans 'users.report.options.insults' },
+      { id: 'Spam', text: osu.trans 'users.report.options.spam' },
+      { id: 'UnwantedContent', text: osu.trans 'users.report.options.unwanted_content' },
+      { id: 'Nonsense', text: osu.trans 'users.report.options.nonsense' },
+      { id: 'Other', text: osu.trans 'users.report.options.other' },
+    ]
+
     @ref = createRef()
     @textarea = createRef()

     @state =
-      selectedReason: options[0]
+      selectedReason: @options[0]
       showingModal: false

@@ -124,7 +125,7 @@
           blackout: false
           bn: "#{bn}-select-options"
           onItemSelected: @onItemSelected
-          options: options
+          options: @options
           selected: @state.selectedReason

       div


Simplify resolve config

Simplify resolve config

This works better. Still need to understand why it doesn't find loaders
from antwar core correctly.

--- webpack.coffee
+++ webpack.coffee
@@ -5,13 +5,14 @@
 getCommon = (config) ->
   return {
     resolve:
+      root: path.join(__dirname, 'node_modules')
       alias:
         'underscore': 'lodash'
         'pages': path.join process.cwd(), 'pages'
         'posts': path.join process.cwd(), 'posts'
         'drafts': path.join process.cwd(), 'drafts'
         'elements': path.join __dirname, 'elements'
-        'theme': config.theme
+        'theme': config.theme,
       extensions: [
         ''
         '.webpack.js'
@@ -21,11 +22,8 @@
         '.json'
       ]
       modulesDirectories: [
+        path.join process.cwd(), 'node_modules'
         'node_modules'
-        path.join __dirname, 'node_modules'
-        path.join process.cwd(), 'node_modules'
-        'scripts'
-        'web_modules'
       ]
     jshint:
       bitwise: false


Movie posters now 300px res version.Way more perfomant

--- scripts/app.coffee
+++ scripts/app.coffee
@@ -57,7 +57,10 @@
   url: ->
     base = @urlRoot || ""
     "#{base.replace(/([^\/])$/, '$1/')}#{encodeURIComponent(this.id)}?callback=?"
-
+
+  parse: (res) ->
+    res.poster300 = res?.poster?.replace(/(\.(\w+))$/i,'-300$1')
+    res

 #
 # Catalog Movie (MODEL)
@@ -138,8 +141,8 @@

     trailerURL = if extendedMetadata? then extendedMetadata.get 'trailer' else null

-    coverUrl = if extendedMetadata? && extendedMetadata.get('images')?.poster? then extendedMetadata.get('images')?.poster else null
-
+    coverUrl = if extendedMetadata? then extendedMetadata.get('poster300') else ""
+
     data =
       rating: @model.get 'MovieRating'
       coverSrc: coverUrl


Cabal Comment Toggling

Cabal Comment Toggling

--- scoped-properties/language-haskell.cson
+++ scoped-properties/language-haskell.cson
@@ -3,3 +3,6 @@
     'commentStart': '-- '
     'increaseIndentPattern': '((^.*(=|\\bdo|\\bwhere|\\bthen|\\belse|\\bof)\\s*$)|(^.*\\bif(?!.*\\bthen\\b.*\\belse\\b.*).*$))'
     'decreaseIndentPattern': '^\\s*$'
+'.source.cabal':
+  'editor':
+    'commentStart':'-- '


add tag route

add tag route

--- node/server.coffee
+++ node/server.coffee
@@ -42,6 +42,9 @@

 gravity.get '/api/v1/fair/:id', (req, res) ->
   res.send fabricate('fair')
+
+gravity.get '/api/v1/tag/:id', (req, res) ->
+  res.send fabricate 'tag'

 gravity.get '/api/v1/fair/:id/sections', (req, res) ->
   res.send [


maybe a scoping issue?

maybe a scoping issue?

--- scripts/pokebattle.coffee
+++ scripts/pokebattle.coffee
@@ -168,9 +168,9 @@
     msg.send "A wild #{poke} appears!"
     msg.send "http://serebii.net/Pkmn_Green/#{pad(pokemon.indexOf(poke) + 1, 3)}.gif"

-
+self = this
 for poke of pokemon
-  module.exports = (robot) ->
+  self.module.exports = (robot) ->
     robot.hear new RegExp(poke, 'i'), (msg) ->
       msg.send "#{poke}, I choose you!"
       msg.send "http://serebii.net/Pkmn_Green/#{pad(pokemon.indexOf(poke) + 1, 3)}.gif"


Auto fill when scrape

Auto fill when scrape

--- view/coffee/creator/create_diagram.coffee
+++ view/coffee/creator/create_diagram.coffee
@@ -14,6 +14,7 @@
         end: "2300"
         period: 60
       stations: []
+      matcher: undefined
       scrape: ""
     methods:
       getTypes: ->
@@ -24,6 +25,7 @@
           @stations = json
           for s in @stations
             s.name = "#{s.line.name} #{s.station.name}"
+          @matcher = stationMatcher(@stations)
           @setAutoCompleteAll()
       getScrape: ->
         ids = @parseScrapeUrl(@scrape)
@@ -42,10 +44,16 @@
       setAutoComplete: (elem) ->
         elem.typeahead('destroy')
         design = {hint: true, highlight: true}
-        config = {name: 'stations', display: 'name', source: stationMatcher(@stations)}
+        config = {name: 'stations', display: 'name', source: @matcher}
         elem.typeahead(design, config)
       setAutoCompleteAll: ->
         @setAutoComplete($('.autoCompleteStation'))
+        @setStationIfOne()
+      setStationIfOne: ->
+        @stops.forEach (stop) =>
+          @matcher stop.name, (matches) ->
+            if matches.length == 1
+              stop.name = matches[0].name
       pushPattern: ->
         start = parseTime(@pattern.start)
         end = parseTime(@pattern.end)


fix NaN for inexistent api_alv

fix NaN for inexistent api_alv

--- views/components/ship-parts/topalert.cjsx
+++ views/components/ship-parts/topalert.cjsx
@@ -51,17 +51,18 @@
       tempTyku = 0.0
       # Basic tyku

+      tempAlv = if item.api_alv? then item.api_alv else 0
       if item.api_type[3] in [6, 7, 8]
         tempTyku += Math.sqrt(ship.api_onslot[slotId]) * item.api_tyku
-        tempTyku += aircraftLevelBonus[item.api_type[3]][item.api_alv]
-        minTyku += Math.floor(tempTyku + Math.sqrt(aircraftExpTable[item.api_alv] / 10))
-        maxTyku += Math.floor(tempTyku + Math.sqrt(aircraftExpTable[item.api_alv + 1] / 10))
+        tempTyku += aircraftLevelBonus[item.api_type[3]][tempAlv]
+        minTyku += Math.floor(tempTyku + Math.sqrt(aircraftExpTable[tempAlv] / 10))
+        maxTyku += Math.floor(tempTyku + Math.sqrt(aircraftExpTable[tempAlv + 1] / 10))

       else if item.api_type[3] == 10 && (item.api_type[2] == 11 || item.api_type[2] == 45)
         tempTyku += Math.sqrt(ship.api_onslot[slotId]) * item.api_tyku
-        tempTyku += aircraftLevelBonus[item.api_type[2]][item.api_alv]
-        minTyku += Math.floor(tempTyku + Math.sqrt(aircraftExpTable[item.api_alv] / 10))
-        maxTyku += Math.floor(tempTyku + Math.sqrt(aircraftExpTable[item.api_alv + 1] / 10))
+        tempTyku += aircraftLevelBonus[item.api_type[2]][tempAlv]
+        minTyku += Math.floor(tempTyku + Math.sqrt(aircraftExpTable[tempAlv] / 10))
+        maxTyku += Math.floor(tempTyku + Math.sqrt(aircraftExpTable[tempAlv + 1] / 10))

   min: minTyku
   max: maxTyku


npe-x500-m3.coffee: Updating links to getting started guide

Signed-off-by: Gareth Davies <2f9181a725196cd0a5e07c6a6e161d83fcc71157@balena.io>

--- npe-x500-m3.coffee
+++ npe-x500-m3.coffee
@@ -19,9 +19,9 @@

 	instructions: commonImg.instructions
 	gettingStartedLink:
-		windows: 'https://docs.resin.io/raspberrypi3/nodejs/getting-started/#adding-your-first-device'
-		osx: 'https://docs.resin.io/raspberrypi3/nodejs/getting-started/#adding-your-first-device'
-		linux: 'https://docs.resin.io/raspberrypi3/nodejs/getting-started/#adding-your-first-device'
+		windows: 'https://www.balena.io/docs/learn/getting-started/raspberrypi3/nodejs/'
+		osx: 'https://www.balena.io/docs/learn/getting-started/raspberrypi3/nodejs/'
+		linux: 'https://www.balena.io/docs/learn/getting-started/raspberrypi3/nodejs/'
 	supportsBlink: true

 	options: [ networkOptions.group ]


Domai.nr implementation commented-out, lets use ours mixed with ResellerClub

--- node_modules_koding/domainer/lib/reseller/DomainService.coffee
+++ node_modules_koding/domainer/lib/reseller/DomainService.coffee
@@ -57,6 +57,15 @@
           return callback err, {available}  if err
           callback err, {available, price}

+  # Poor-mans guess mechanism ~ GG
+  guessDomain: (domain)->
+    suggestions = []
+    for tld in availableTLDs
+      suggestions.push "#{domain}.#{tld}"
+      if domain.indexOf(tld) > 0
+        suggestions.push domain.replace tld, ".#{tld}/"
+    suggestions
+
   getDomainSuggestions: (domainName, callback) ->
     suggestions = []

@@ -69,6 +78,27 @@
       return callback null, suggestions

     {domain, extension} = splitDomain domainName
+
+    # suggestions = @guessDomain domainName
+
+    options =
+      service           : "domains"
+      action            : "available"
+      parameters        :
+        "domain-name"   : domain
+        tlds            : availableTLDs
+
+    @makeRequestToResellerApi options, (err, response)->
+      return callback err, suggestions  if err
+
+      for own domain, current of response
+        {prefix, extension} = splitDomain domain
+        if current.status is 'available'
+          attachPrice {domain, extension}
+          foundASuggestion = yes
+
+      unless foundASuggestion
+        callback null, suggestions

     # Following is added for using Reseller's suggestion
     # Use it if domai.nr becames useless. ~ GG
@@ -98,23 +128,28 @@
     #   unless foundASuggestion
     #     callback null, suggestions

-    @makeSuggestionRequestToDomainr domain, (err, suggestionResponse)->
-      return callback err, suggestions  if err
-
-      if suggestionResponse.results.length is 0
-        return callback null, suggestions
-
-      for result in suggestionResponse.results
-        if result.availability is 'available'
-          {domain, extension} = splitDomain result.domain
-          if extension not in blackListedTLDs  \
-             and result.domain isnt domainName \
-             and extension in availableTLDs
-            attachPrice {domain:result.domain, extension}
-            foundASuggestion = yes
-
-      unless foundASuggestion
-        callback null, suggestions
+    # ---------------------------
+
+    # Domai.nr API Implementation
+    # which return false availability most of the time
+
+    # @makeSuggestionRequestToDomainr domain, (err, suggestionResponse)->
+    #   return callback err, suggestions  if err
+
+    #   if suggestionResponse.results.length is 0
+    #     return callback null, suggestions
+
+    #   for result in suggestionResponse.results
+    #     if result.availability is 'available'
+    #       {domain, extension} = splitDomain result.domain
+    #       if extension not in blackListedTLDs  \
+    #          and result.domain isnt domainName \
+    #          and extension in availableTLDs
+    #         attachPrice {domain:result.domain, extension}
+    #         foundASuggestion = yes
+
+    #   unless foundASuggestion
+    #     callback null, suggestions

   getTldPrice: (tld, callback) ->
     if prices[tld]? then callback null, prices[tld]


Fix privacy on definitions and other API of the compiler

--- src/compiler.coffee
+++ src/compiler.coffee
@@ -208,7 +208,7 @@
     @types = []
     @isMalformed = no
     @modulePath = null
-    @submodules = newSet()
+    @submodules = newMap()
     @importedModules = newSet()
     @_requested = newMap()

@@ -227,8 +227,8 @@
   isRequesting: ->
     @_requesting

-  addSubmodule: (name) ->
-    addToSet @submodules, name
+  addSubmodule: (name, isExported) ->
+    addToMap @submodules, name, isExported

   isSubmodule: (name) ->
     inSet @submodules, name
@@ -376,7 +376,6 @@
     scope.deferred = []
     scope.deferredBindings = []
     scope.boundTypeVariables = newSet()
-    scope.exportedNames = newSet()
     scope.classes = newMap()
     scope.typeNames = newMap()
     scope.typeAliases = newMap()
@@ -406,8 +405,11 @@
   isInTopScope: ->
     @_scope().topLevel

-  exportName: (name) ->
-    addToSet @_scope().exportedNames, name
+  exportDeclared: ->
+    @_exporting = yes
+
+  stopExporting: ->
+    @_exporting = no

   addTypeName: (dataType) ->
     if dataType instanceof TypeApp
@@ -600,6 +602,7 @@

   _declare: (name, declaration) ->
     declaration.id ?= @freshId()
+    declaration.exported = @_exporting
     replaceOrAddToMap @_scope(), name, declaration

   _preDeclaration: (name) ->
@@ -695,6 +698,9 @@

   tempType: (name) ->
     (@_declaration name)?.tempType
+
+  importable: (name) ->
+    (@_declaration name)?.exported

   freshTypeVariable: (kind) ->
     if not kind
@@ -1498,12 +1504,12 @@
   #   (exportAll ctx, (join (importAny defaultImports), (topLevel ctx, form))))]
   definitions = (join (importAny defaultImports), (topLevel ctx, form))
   shouldBeExported = (name, declaration) ->
-    not declaration.virtual and declaration.final and isExported name
-  isExported = (name) ->
-    inSet ctx._scope().exportedNames, name
+    not declaration.virtual and declaration.final
+  isExported = (name, exported) ->
+    exported
   nonVirtual = filterMap shouldBeExported, ctx._scope()
   exported = concatSets (subtractSets nonVirtual, builtInDefinitions()),
-    (filterSet isExported, ctx.submodules) # TODO: separate exporting namespaces according to the separation of namespaces
+    (filterMap isExported, ctx.submodules) # TODO: separate exporting namespaces according to the separation of namespaces
   exportList = map validIdentifier, (setToArray exported)
   exportDictionary = (jsDictionary exportList, exportList)
   [..., type] = typedModulePath.types
@@ -1714,7 +1720,7 @@
       if result.request
         ctx.req result.request, newSet()
       else
-        ctx.addSubmodule name
+        ctx.addSubmodule name, isExported
         if result.errors
           ctx.extendSubstitution fails: result.errors # propagate errors TODO: rename substitution which is only used for type errors now
         jsValue result.js
@@ -1724,9 +1730,10 @@
 ms.export = ms_export = (ctx, call) ->
   requireName ctx, 'Name to export required'
   name = ctx.definitionName()
-  if name
-    ctx.exportName name
-  expressionCompile ctx, _fst _arguments call
+  ctx.exportDeclared()
+  compiled = expressionCompile ctx, _fst _arguments call
+  ctx.stopExporting()
+  compiled

 # fn+ (syntax [..args]
 #   (` export (fn ,..args)))
@@ -2001,6 +2008,10 @@
     (applyKindFn (new TypeConstr dataName, dataKind), typeVars...))
   fieldTypes: (map ((types) -> substituteList freshingSub, types), fieldTypes)

+
+ms['record+'] = ms_record_export = (ctx, call) ->
+  (call_ (token_ 'export'), [(call_ (token_ 'record'), (_arguments call))])
+
 ms.record = ms_record = (ctx, call) ->
     args = _validArguments call
     hasName = requireName ctx, 'Name required to declare new record'
@@ -2091,6 +2102,10 @@
     (jsAssign whatCompiled, toCompiled)
   else
     jsNoop()
+
+
+ms['class+'] = ms_class_export = (ctx, call) ->
+  (call_ (token_ 'export'), [(call_ (token_ 'class'), (_arguments call))])

 # Adds a class to the scope or defers if superclass doesn't exist
 ms.class = ms_class = (ctx, call) ->
@@ -2189,6 +2204,10 @@
     ctx.declare name, {arity, docs, type, virtual: yes}
   return

+
+ms['instance+'] = ms_instance_export = (ctx, call) ->
+  (call_ (token_ 'export'), [(call_ (token_ 'instance'), (_arguments call))])
+
 ms.instance = ms_instance = (ctx, call) ->
     hasName = requireName ctx, 'Name required to declare a new instance'

@@ -2431,8 +2450,7 @@
     return malformed ctx, call, 'req requires a module name to require from'
   moduleNameAtom.label = 'module'
   declaredModuleName = moduleNameAtom.symbol
-  requiredNames =
-  names:  (arrayToSet (filter _is, (map _symbol, reqs)))
+  requiredNames = (arrayToSet (filter _is, (map _symbol, reqs)))
   moduleName = (resolveModuleName ctx, declaredModuleName)
   ctx.req moduleName, requiredNames
   if ctx.isModuleLoaded moduleName
@@ -2441,9 +2459,11 @@
         name = arg.symbol
         if ctx.isFinallyTyped name, ctx.currentScopeIndex()
           malformed ctx, arg, "#{name} already declared"
-        else if not ctx.isCurrentlyDeclared arg.symbol
+        else if not ctx.isCurrentlyDeclared name
           # TODO: this will not trigger if we define in current/other module
           malformed ctx, arg, "#{name} was not declared in #{moduleName}"
+        else if not ctx.importable name
+          malformed ctx, arg, "#{name} was not exported from #{moduleName}"
         else
           arg.id = ctx.currentDeclarationId name
           arg.imported =
@@ -3621,22 +3641,22 @@
   else if importedTypedModulePath.nonExportedLink
     return malformed ctx, moduleNameAtom, "Module #{importedTypedModulePath.nonExportedLink} must be exported."
   {names: pathNames, types} = importedTypedModulePath
-  baseType = types[i]
+  baseType = types[0]
   numModules = (filter ((type) -> type is baseType), types).length
   moduleHandle =
     switch baseType
       when 'commonJs'
         jsCall 'require', (toJsString (pathNames[0...numModules].join '/'))
       when 'browser'
-        acc = "Shem"
+        acc = 'Shem'
         for i in [0...numModules]
           acc = (jsAccess acc, validIdentifier pathNames[i])
         acc
   temp = ctx.newJsVariable()
-  join [jsVarDeclaration temp, moduleHandle],
-    for name in names
-      validName = (validIdentifier name)
-      jsVarDeclaration validName, (jsAccess temp, validName)
+  jsStatementList (join [jsVarDeclaration temp, moduleHandle],
+      for name in names
+        validName = (validIdentifier name)
+        jsVarDeclaration validName, (jsAccess temp, validName))

 isTypeAnnotation = (expression) ->
   (isCall expression) and (':' is _symbol _operator expression)
@@ -3950,6 +3970,13 @@

 jsReturnTranslate = ({result}) ->
   "return #{result};"
+
+
+jsStatementList = (statements) ->
+  {js: jsStatementListTranslate, statements}
+
+jsStatementListTranslate = ({statements}) ->
+  listOfLines statements

 jsTernary = (cond, thenExp, elseExp) ->
@@ -5926,32 +5953,36 @@
   # types: typeEnumaration ctx
   errors: errors

-compileModuleWithDependencies = (moduleName = '@unnamed') ->
-  js: library + immutable + (listOfLines map lookupJs, (setToArray runtimeDependencies moduleName))
-
-compileExpression = (source, moduleName = '@unnamed') ->
-  {js} = parsed = parseExpression source, moduleName
+compileModuleWithDependencies = (typedModulePath = defaultTypedModulePath) ->
+  js: library + immutable + (listOfLines map lookupJs,
+    (setToArray runtimeDependencies (modulePathToName typedModulePath.names)))
+
+compileExpression = (source, typedModulePath = defaultTypedModulePath) ->
+  {js} = parsed = parseExpression source, typedModulePath
   extend parsed,
-    (js:  library + immutable + (listOfLines map lookupJs, (setToArray runtimeDependencies moduleName)) + '\n;' + js)
-
-parseTopLevel = (source, moduleName = '@unnamed') ->
+    (js:  library + immutable + (listOfLines map lookupJs,
+      (setToArray runtimeDependencies (modulePathToName typedModulePath.names))) + '\n;' + js)
+
+parseTopLevel = (source, typedModulePath = defaultTypedModulePath) ->
   ast = (astFromSource "(#{source})", -1, -1)
-  parseWith ast, ast, topLevel, moduleName, yes
-
-parseExpression = (source, moduleName = '@unnamed') ->
+  parseWith ast, ast, topLevel, typedModulePath, yes
+
+parseExpression = (source, typedModulePath = defaultTypedModulePath) ->
   ast = (astFromSource "(#{source})", -1, -1)
   [expression] = _terms ast
-  compilationFn = (topLevelExpressionInModule importsFor moduleDependencies moduleName)
-  parseWith ast, expression, compilationFn, moduleName, no
-
-parseWith = (originalAst, ast, compilationFn, moduleName, doDeclare) ->
+  compilationFn = (topLevelExpressionInModule importsFor moduleDependencies (modulePathToName typedModulePath.names))
+  parseWith ast, expression, compilationFn, typedModulePath, no
+
+parseWith = (originalAst, ast, compilationFn, typedModulePath, doDeclare) ->
   if _empty _validTerms originalAst
     {
       ast: originalAst
       js: ''
     }
   else
+    moduleName = modulePathToName typedModulePath.names
     {modules, ctx} = contextWithDependencies moduleDependencies moduleName
+    ctx.typedModulePath = typedModulePath
     {js} = compileCtxAstToJs compilationFn, ctx, ast
     errors = checkTypes ctx
     (finalizeTypes ctx, ast)
@@ -6060,9 +6091,9 @@
       throw new Error "Macro #{name} already defined"
     else
       addToMap topScope.macros, name, macro
-  for name, {type, arity, docs, source, isClass, virtual, final} of values definitions when shouldImport name
+  for name, {type, arity, docs, source, isClass, virtual, final, exported} of values definitions when shouldImport name
     addToMap topScope, name, {
-      arity, docs, source, isClass, virtual, final,
+      arity, docs, source, isClass, virtual, final, exported,
       type: (type if shouldDeclare)
       tempType: type
       id: ctx.freshId()}


allow feature to be defined inside composition, do not define rpc inside local dynamic resolved map

--- src/composer.coffee
+++ src/composer.coffee
@@ -20,20 +20,21 @@
     else
       # TODO: consider making these part of yang-core-composer spec
       @define 'extension', 'composition',
-          type:   '1'
-          source: '1'
-          construct: (arg, params, children, ctx) ->
-            console.debug? "passing through contents of composition"
-            @copy ctx, children
-            undefined
+        module: '0..n'
+        specification: '0..n'
+        feature: '0..n'
+        source: '1'
+        construct: (arg, params, children, ctx) ->
+          console.debug? "passing through contents of composition"
+          @copy ctx, children
+          undefined
       @define 'extension', 'source',
-          argument: 'data'
-          preprocess: (arg, params, ctx) ->
-            data = (new Buffer arg, 'base64').toString 'binary'
-            { schema } = yang.preprocess data, this
-            @copy ctx, schema
-            delete ctx.source # no longer needed
-
+        argument: 'data'
+        preprocess: (arg, params, ctx) ->
+          data = (new Buffer arg, 'base64').toString 'binary'
+          { schema } = yang.preprocess data, this
+          @copy ctx, schema
+          delete ctx.source # no longer needed

   # register spec/schema search directories (exists)
   include: ->
@@ -73,21 +74,24 @@

     match = super keys..., warn: false
     match ?= switch
-      when keys[0] is 'module'
+      when 'module' is keys[0]
         @use (@includes.fetch keys[1])...
         super keys..., recurse: false
-      when keys[0] in [ 'feature', 'rpc' ] and opts.module?
-        [ type, key ] = keys
-        loc = (@links.resolve "#{opts.module}/#{type}/#{key}")[0]
-        @set type, key, switch
+      when 'feature' is keys[0] and opts.module?
+        loc = (@links.resolve [opts.module].concat(keys).join '/')[0]
+        @set keys..., switch
           when loc?
             res = require loc
             res.__origin__ = loc
             res
           else
-            console.debug? "unable to resolve '#{opts.module}/#{type}/#{key}'"
+            console.debug? "unable to resolve '#{opts.module}/#{keys.join '/'}'"
             {}
         super keys..., recurse: false
+      when 'rpc' is keys[0] and opts.module?
+        loc = (@links.resolve [opts.module].concat(keys).join '/')[0]
+        # TODO: should 'browserify' require loc and save it in 'specification'
+        require loc if loc?
     return match

 module.exports = Composer


cooldown finilazation

cooldown finilazation

--- src/cooldown.coffee
+++ src/cooldown.coffee
@@ -13,7 +13,7 @@

     target ||= 0
     approachBy = 1 unless approachBy?
-    value =
+    value = op

     unless I.cooldowns[name]
       I.cooldowns[name] = {


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/cooldown.coffee
+++ src/cooldown.coffee
@@ -41,7 +41,7 @@

     player.update(1)

-    player.I.shootTimer # => 9
+    player.I.shootTimer # => 99

 @name Cooldown
 @module


Added test cases to make sure the uid on object/array/date keys are not share with other maps.

--- spec/Map.spec.coffee
+++ spec/Map.spec.coffee
@@ -112,7 +112,13 @@
 		expect(map.get(date2)).toBe "date 2"
 		expect(map.get(new Date())).toBeUndefined()
 		expect(map.has(new Date())).toBeFalsy()
-
+	map2 = new Map()
+	it "should store a id on the key that's unique to each map", ->
+		expect(map2.get(date1)).toBeUndefined()
+		map2.set(date1, "date 1 on map 2")
+		expect(map.get(date1)).toBe "date 1"
+		expect(map2.get(date1)).toBe "date 1 on map 2"
+
 describe "Delete", ->
 	map = new Map()
 	it "should return false for deleting nonexistent keys", ->


removed describe.only

removed describe.only

--- spec/Thenable.coffee
+++ spec/Thenable.coffee
@@ -1348,7 +1348,7 @@
           done()

-    describe.only 'merge nested branches w/ contest', ->
+    describe 'merge nested branches w/ contest', ->

       merger = (splitter) ->
         return (choice) ->
@@ -1541,7 +1541,6 @@

       it 'v8', (done) ->
-
         splitter = (choice,data) ->
           choice.tree()
           .deliver('-')


Don't need to repeat ourselves here.

Don't need to repeat ourselves here.

--- spec/git-spec.coffee
+++ spec/git-spec.coffee
@@ -255,7 +255,6 @@
         statusHandler.callCount > 0

       runs ->
-        filePath = path.join(subDir, 'b.txt')
         status = repo.getCachedPathStatus(filePath)
         expect(repo.isStatusModified(status)).toBe false
         expect(repo.isStatusNew(status)).toBe false


删除调试用注释

删除调试用注释

--- routes/api.coffee
+++ routes/api.coffee
@@ -167,12 +167,10 @@
             status = false
             return false
       if status
-        #为空,删除文件夹后向上遍历
+        #为空,删除文件夹后向上递归
         fs.rmdir path
         newPath = path.split('/')
-        #console.log newPath
         newPath.pop()
-        #console.log newPath
         isEmpty newPath.join('/'),callback
       else
         #不为空,执行 callback


Add email to sendgrid welcome template substitutions

--- server/models/User.coffee
+++ server/models/User.coffee
@@ -372,7 +372,7 @@
   else sendgrid.templates.welcome_email_user
   msg =
     to:
-      email: @get('email')
+      email: @get('emailLower')
       name: @broadName()
     from:
       email: 'team@codecombat.com'
@@ -382,6 +382,7 @@
       name: @broadName()
       verify_link: makeHostUrl(req, "/user/#{@_id}/verify/#{@verificationCode((new Date).getTime())}")
       teacher: @isTeacher()
+      email: @get('emailLower')
   try
     yield sendgrid.api.send(msg)
   catch err


Bugfix.

Bugfix.

--- server/publication.coffee
+++ server/publication.coffee
@@ -430,7 +430,7 @@
   @related (author, person) =>
     return unless author?._id

-    if person?.person
+    if person?.isAdmin
       Publication.documents.find
         'authors._id': author._id
         cached:
@@ -483,7 +483,7 @@
   return unless id

   @related (person) =>
-    if person?.person
+    if person?.isAdmin
       Publication.documents.find
         _id: id
         cached:
@@ -530,7 +530,7 @@
   return unless ids?.length

   @related (person) =>
-    if person?.person
+    if person?.isAdmin
       Publication.documents.find
         _id:
           $in: ids


Draw the preloader on the device screen, but keep the scale so it’s still the same size.

--- framer/Extras/Preloader.coffee
+++ framer/Extras/Preloader.coffee
@@ -11,17 +11,18 @@

 		@timeout = 5

-		@context = new Context({name: "Preloader"})
+		@context = new Context({parent: Framer.CurrentContext, name: "Preloader"})

 		@context.run =>

 			@cover = new Layer
-				size: 200
+				frame: @context
 				backgroundColor: "white"
 				opacity: 0

 			@progressIndicator = new CircularProgressComponent
 				size: 160
+				point: Align.center
 				parent: @cover

 			@progressIndicator.railsColor = Color.grey(0, 0.1)
@@ -30,14 +31,16 @@
 			@brand = new Layer
 				width: 48
 				height: 72
+				point: Align.center
 				parent: @cover
 				backgroundColor: null
 			@brand.style["background-image"] = "url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGAAAACQCAYAAAD3Cm4hAAAABGdBTUEAALGPC/xhBQAAA5xJREFUeAHt3O9t2zAQh2EpyBbtUp2k3SDOBu2nbpHu0FmaOVzRDv0ntmSJPJJ3vJeAQcIOeNLvCekPMjj+ft/vhmF4mV60BgmMoSYIDZL/KHkAAEEBAAhtEE4rIJZnO4pJ1OlvAEJZEOqEH6rcBQgfgBBSKN9mAUJpEBoDgKAAAISyCItb0GVptqPLNOTGqwFCSRDkgo8zbQIAIcYm128GAEEu/DBTEgAIcgjJACDIIGQBgJCPkA0AQh6CCAAI6QhiACCkIYgCgLAdQRwAhG0IRQBAWI9QDACEdQhFAUB4jFAcAIRlhCoAIMwjVAMA4T5CVQAQbhGqA4BwjdAEAIQzQjMAEI4ITQFAyHgkeV5E+SPPv7ZovgIin1cENQBetyNVAB4R1AF4Q1AJ4AlBLYAXBNUAHhDUA/SOYAKgZwQzAL0imALoEcEcQG8IJgF6QjAL0AvC+OVtvw83Y7WN4/D679u4s3r9T1YvPF739O/z8vXP4dCp+Jap3jxASNsyQhcAlhG6AbCK0BWARYTuAKwhdAlgCaFbACsIXQNYQOgeQDuCCwDNCG4AtCK4AtCI4A5AG4JLAE0IbgG0ILgG0IDgHqA1AgBBYGqtHuoAcMy/GQIAFwBhWHslAPAJoDYCAHcAaiIAMANQCwGABYAaCAA8ACiNAMAKgJIIAKwEKIUAwAaAEggAbASQRgAgAUASAYBEACkEADIAJBAAyATIRQBAACAHAQAhgFQEAAQBUhAAEAbYigBAAYAtCAAUAliLAEBBgDUIABQGeIQAQAWAJQQAKgHMIQBQEeAeAgCVAT4jPDeoT8kpgY9f4A3P73+H12E68sVsKr+mE4MMt6fh53TY0XTokeF7MH3px+8AEJohnr+EQWiCcAYI5UGojnANAIICABCqItyugFie7SgmUbSfBwhlQSgafph8GSD8BQghhWLtMUAoDUJjABAUAIBQBGHdFnRZmu3oMo3s8XaAUBKE7ODjBGkAIMT8svt0ABCyww8T5AGAkI2QDwBCFoIMAAjJCHIAICQhyAKAsBlBHgCETQhlAEBYjVAOAIRVCGUBQHiIUB4AhEWEOgAgzCLUAwDhLkJdABBuEOoDgHCF0AYAhBNCOwAQDghtAUAQeCBzWkwZA8fPmNuvgOjmFEEPgNPtSBeAQwR9AM4QdAI4QtAL4ARBN4ADBP0AnSPYAOgYwQ5Apwi2ADpFCLdlr/3Y74bv06EvtIYJBATj7T+6xt5DGDRVPAAAAABJRU5ErkJggg==')"

 			do layout = =>
-				@cover.frame = Canvas
-				@progressIndicator.point = Align.center
-				@brand.point = Align.center
+				screen = Framer.Device.screen
+				scale = screen.frame.width / screen.canvasFrame.width
+				@progressIndicator.scale = scale
+				@brand.scale = scale

 			Canvas.onResize(layout)


Transfer nodes linked to a collector have input divided by 100 if the source node has no inputs _and_ there is no transfer modifier.

--- src/code/models/simulation.coffee
+++ src/code/models/simulation.coffee
@@ -68,7 +68,7 @@

   deltaValue = 0
   for link in links
-    {sourceNode, relation, transferNode} = link
+    {sourceNode, targetNode, relation, transferNode} = link
     inV = nodeValues[sourceNode.key]
     outV = startValue
     switch relation.type
@@ -77,13 +77,16 @@

       when 'transfer'
         transferValue = nodeValues[transferNode.key]
-        # transfer values are scaled unless they have a modifier
-        if not transferNode.inLinks('transfer-modifier').length
+        # transfer values are scaled if they have no modifier and
+        # their source is an independent node (has no inputs)
+        sourceInputCount = sourceNode.inLinks().length
+        transferModifierCount = transferNode.inLinks('transfer-modifier').length
+        if sourceInputCount is 0 and transferModifierCount is 0
           transferValue /= 100

-        if link.sourceNode is @
+        if sourceNode is @
           deltaValue -= transferValue
-        else if link.targetNode is @
+        else if targetNode is @
           deltaValue += transferValue

   # accumulators hold their values in previousValue which is confusing


Remove duplicated directive datepicker from index_utils directory

--- app/assets/javascripts/admin/utils/directives/datepicker.js.coffee
+++ app/assets/javascripts/admin/utils/directives/datepicker.js.coffee
@@ -1,9 +0,0 @@
-angular.module("admin.utils").directive "datepicker", ->
-  require: "ngModel"
-  link: (scope, element, attrs, ngModel) ->
-    element.datepicker
-      dateFormat: "yy-mm-dd"
-      onSelect: (dateText, inst) ->
-        scope.$apply (scope) ->
-          # Fires ngModel.$parsers
-          ngModel.$setViewValue dateText


Fixes typo.

Fixes typo.

--- app/assets/javascripts/app/controllers/agent_ticket_zoom.js.coffee
+++ app/assets/javascripts/app/controllers/agent_ticket_zoom.js.coffee
@@ -333,9 +333,9 @@
 #    @log 'reply ', article, @el.find('[name="to"]')

     # add quoted text if needed
-    if window.Session['UISeletion']
+    if window.Session['UISelection']
       body = @el.find('[name="body"]').val() || ''
-      selection = window.Session['UISeletion'].trim()
+      selection = window.Session['UISelection'].trim()
       selection = selection.replace /^(.*)$/mg, (match) =>
         '> ' + match
       body = body + selection


Display the number of selected items on bulk actions

--- app/assets/javascripts/effective_datatables/bulk_actions.js.coffee
+++ app/assets/javascripts/effective_datatables/bulk_actions.js.coffee
@@ -19,11 +19,12 @@

 toggleDropdown = ($wrapper) ->
   $bulkActions = $wrapper.children().first().find('.buttons-bulk-actions').children('button')
+  selected = $wrapper.find("input[data-role='bulk-action']:checked").length

-  if $wrapper.find("input[data-role='bulk-action']:checked").length > 0
-    $bulkActions.removeAttr('disabled')
+  if selected > 0
+    $bulkActions.removeAttr('disabled').text("Bulk Actions (#{selected} items)")
   else
-    $bulkActions.attr('disabled', 'disabled')
+    $bulkActions.attr('disabled', 'disabled').text('Bulk Actions')

 restoreSelected = ($table, selected) ->
   $bulkActions = $table.closest('.dataTables_wrapper').children().first().find('.buttons-bulk-actions').children('button')


[skip] Gave feedback when "Copy" works (1m)

--- app/assets/javascripts/houston/feedback/views/comments_view.coffee
+++ app/assets/javascripts/houston/feedback/views/comments_view.coffee
@@ -663,6 +663,7 @@
       e.preventDefault()

     document.execCommand "copy"
+    alertify.success("Feedback copied!")

   copyUrl: (e)->
     e.preventDefault()
@@ -678,6 +679,7 @@
       e.preventDefault()

     document.execCommand "copy"
+    alertify.success("Short URL copied!")


Add findOne spec, Fix findIds spec

Add findOne spec, Fix findIds spec

--- spec/read_aggregate_repository_spec.coffee
+++ spec/read_aggregate_repository_spec.coffee
@@ -22,6 +22,8 @@
     EventStoreStub.find.yields null, [
       name: 'create'
       aggregate:
+        id: 23
+        name: 'Foo'
         changed:
           props:
             name: 'John'
@@ -46,7 +48,7 @@
         done()

     it 'should return a instantiated ReadAggregate containing the applied DomainEvents', (done) ->
-      readAggregate = readAggregateRepository.findById 'ReadFoo', 23, (err, readAggregate) ->
+      readAggregateRepository.findById 'ReadFoo', 23, (err, readAggregate) ->
         expect(readAggregate.name).to.be 'John'
         done()

@@ -81,16 +83,20 @@
         expect(readAggregates[0]).to.be.a ReadFoo
         done()

-  describe.skip '#findOne', ->
+  describe '#findOne', ->

-    it 'should call find and return only one result'
+    it 'should call find and return only one result', (done) ->
+      findStub = sandbox.stub readAggregateRepository, 'find'
+      findStub.yields null, [1, 2]
+      readAggregateRepository.findOne 'ReadFoo', {}, (err, result) ->
+        expect(result).to.be 1
+        done()

-  describe.skip '#findIds', ->
+  describe '#findIds', ->

-    it 'should return all AggregateIds matching the given query-criteria', ->
-      criteria = {}
-      sandbox.stub readAggregateRepository._adapter, '_findAggregateIdsByDomainEventCriteria', -> [42]
-      aggregateIds = readAggregateRepository.findIds criteria
-      expect(aggregateIds.length).to.be 1
-      expect(aggregateIds[0]).to.be 42
+    it 'should return all AggregateIds matching the given query', (done) ->
+      readAggregateRepository.findIds 'ReadFoo', {}, (err, aggregateIds) ->
+        expect(aggregateIds.length).to.be 1
+        expect(aggregateIds[0]).to.be 23
+        done()


Add yargs to runner to allow selecting files

--- spec/runner.coffee
+++ spec/runner.coffee
@@ -1,10 +1,19 @@
 Jasmine = require "jasmine"
 Path = require "path"
+argv = require("yargs").array("specFiles").argv
+
+specDir = argv.specDir ? "spec"
+specFiles = argv.specFiles ? ["**/*-[sS]pec.?(coffee|js)"]
+
+console.log specDir: specDir, specFiles: specFiles

 exports.run = ->
   jasmine = new Jasmine
-  configFilePath = require("path").resolve __dirname, "..", "spec", "support", "jasmine.json"
-  jasmine.loadConfigFile configFilePath
+
+  jasmine.loadConfig
+    "spec_dir": specDir,
+    "spec_files": specFiles
+
   jasmine.execute()

 exports.run() unless module.parent?


Added comment recording that Knockout started to fail to load in AMD since the release of 3.5.0

--- config/karma/run.coffee
+++ config/karma/run.coffee
@@ -18,11 +18,9 @@
   TEST_GROUPS = require '../test_groups'
   TEST_GROUPS = {browser_globals: TEST_GROUPS.browser_globals.slice(0, 1)} if options.tags.indexOf('@quick') >= 0

-  # 2019: remove AMD tests due to knockout failing to load
-  # TODO: troubleshoot knockout loading in AMD
+  # TODO: disabled AMD tests until can troubleshoot knockout not loading in AMD (it stoped loading at 3.5.0)
+  # TEST_GROUPS = {amd: TEST_GROUPS.amd}
   delete TEST_GROUPS.amd
-
-  # TEST_GROUPS = {browserify: TEST_GROUPS.browserify}

   for name, tests of TEST_GROUPS then do (name, tests) ->
     for test in tests then do (test) ->


Make a few changes to work around cs to es6 converter

--- src/deparser.coffee
+++ src/deparser.coffee
@@ -108,23 +108,9 @@

     throw new Error(type + " is not implemented") unless @[type]?

-    @[type](node, context)
-
-	# 0  AEXPR_OP,					/* normal operator */
-	# 1  AEXPR_OP_ANY,				/* scalar op ANY (array) */
-	# 2  AEXPR_OP_ALL,				/* scalar op ALL (array) */
-	# 3  AEXPR_DISTINCT,				/* IS DISTINCT FROM - name must be "=" */
-	# 4  AEXPR_NULLIF,				/* NULLIF - name must be "=" */
-	# 5  AEXPR_OF,					/* IS [NOT] OF - name must be "=" or "<>" */
-	# 6  AEXPR_IN,					/* [NOT] IN - name must be "=" or "<>" */
-	# 7  AEXPR_LIKE,					/* [NOT] LIKE - name must be "~~" or "!~~" */
-	# 8  AEXPR_ILIKE,				/* [NOT] ILIKE - name must be "~~*" or "!~~*" */
-	# 9  AEXPR_SIMILAR,				/* [NOT] SIMILAR - name must be "~" or "!~" */
-	# 10 AEXPR_BETWEEN,				/* name must be "BETWEEN" */
-	# 11 AEXPR_NOT_BETWEEN,			/* name must be "NOT BETWEEN" */
-	# 12 AEXPR_BETWEEN_SYM,			/* name must be "BETWEEN SYMMETRIC" */
-	# 13 AEXPR_NOT_BETWEEN_SYM,		/* name must be "NOT BETWEEN SYMMETRIC" */
-	# 14 AEXPR_PAREN					/* nameless dummy node for parentheses */
+    func = @[type]
+
+    func.call(this, node, context)

   'A_Expr': (node, context) ->
     output = []
@@ -135,7 +121,9 @@
           output.push '(' + @deparse(node.lexpr) + ')'

         if node.name.length > 1
-          output.push "OPERATOR(" + @deparse(node.name[0]) + "." + @deparse(node.name[1]) + ")"
+          arg1 = @deparse(node.name[0])
+          arg2 = @deparse(node.name[1])
+          output.push 'OPERATOR(' + arg1 + '.' + arg2 + ')'
         else
           output.push(@deparse(node.name[0]))

@@ -165,7 +153,7 @@

       when 5 # AEXPR_OF
         op = if node.name[0].String.str is '=' then 'IS OF' else 'IS NOT OF'
-        list = (@deparse(item) for item in node.rexpr)
+        list = node.rexpr.map (node) => @deparse(node)
         return format('%s %s (%s)', @deparse(node.lexpr), op, list.join(', '))

       when 6 # AEXPR_IN
@@ -241,7 +229,8 @@
     list = []

     if node.elements
-      list = (@deparse(element) for element in node.elements)
+      list = node.elements.map (e) => @deparse(e)
+      # list = (@deparse(element) for element in node.elements)

     output.push list.join(', ')
     output.push(']')
@@ -409,13 +398,13 @@
       order.push (node.agg_order.map (node) => @deparse(node, context)).join(", ")

     call = []
-    call.push(name + '(')
+    call.push "#{name}("
     call.push 'DISTINCT ' if node.agg_distinct

     # prepend variadic before the last parameter
     # SELECT CONCAT('|', VARIADIC ARRAY['1','2','3'])
     if node.func_variadic
-      params[params.length - 1] = 'VARIADIC ' + params[params.length - 1]
+      params[params.length - 1] = "VARIADIC #{params[params.length - 1]}"

     call.push params.join(', ')

@@ -423,7 +412,7 @@
       call.push ' '
       call.push order.join(' ')

-    call.push ')'
+    call.push "#{''})"

     output.push(compact(call).join(''))

@@ -473,7 +462,7 @@
     if node.rarg
       # wrap nested join expressions in parens to make the following symmetric:
       # select * from int8_tbl x cross join (int4_tbl x cross join lateral (select x.f1) ss)
-      if node.rarg.JoinExpr? and not node.rarg.JoinExpr?.alias?
+      if node.rarg.JoinExpr? and not node.rarg.JoinExpr.alias?
         output.push '(' + @deparse(node.rarg) + ')'
       else
         output.push @deparse(node.rarg)
@@ -556,7 +545,7 @@
     for funcCall in node.functions
       call = [ @deparse(funcCall[0]) ]

-      if funcCall[1]?.length
+      if funcCall[1] && funcCall[1].length
         call.push 'AS (' + (funcCall[1].map((def) => @deparse(def))).join(', ') + ')'

       funcs.push call.join(' ')
@@ -650,7 +639,10 @@
       ]

       output.push sets[node.op]
-      output.push 'ALL' if node.all
+
+      if node.all
+        output.push 'ALL'
+
       output.push '(' + @deparse(node.rarg) + ')'

     if node.distinctClause
@@ -781,7 +773,7 @@
         @deparse(item)

     type = []
-    type.push @type(node['names'], args?.join(', '))
+    type.push @type(node['names'], args && args.join(', '))
     type.push '[]' if node.arrayBounds?

     output.push type.join('')
@@ -930,8 +922,8 @@
       intervals = @interval(typmods[0])

       # SELECT interval(0) '1 day 01:23:45.6789'
-      if node.typmods[0]?.A_Const?.val.Integer.ival is 32767 and node.typmods[1]?.A_Const?
-        intervals = ['(' + node.typmods[1]?.A_Const.val.Integer.ival + ')']
+      if node.typmods[0] and node.typmods[0].A_Const and node.typmods[0].A_Const.val.Integer.ival is 32767 and node.typmods[1] and node.typmods[1].A_Const?
+        intervals = ['(' + node.typmods[1].A_Const.val.Integer.ival + ')']

       else
         intervals = intervals.map (part) =>


Change output formatting

Change output formatting

--- src/diabetes.coffee
+++ src/diabetes.coffee
@@ -66,15 +66,15 @@
     options.a1cThreshold = 20

   robot.respond /estimate a1c (from average )?(.*)/i, (msg) ->
-    bg = msg.match[2]
+    bg = parseFloat(msg.match[2])
     mgdl = 0
     mmol = 0

     if bg >= options.threshold
-      mgdl = bg
+      mgdl = bg.toFixed(0)
       mmol = mgdlToMmol(bg).toFixed(1)
     else
-      mmol = bg
+      mmol = bg.toFixed(1)
       mgdl = mmolToMgdl(bg).toFixed(0)

     dcct = mgdlToDcct(mgdl)
@@ -87,15 +87,15 @@
     msg.send reply

   robot.respond /estimate average (from a1c )?(.*)/i, (msg) ->
-    a1c = msg.match[2]
+    a1c = parseFloat(msg.match[2])
     dcct = 0
     ifcc = 0

     if a1c >= options.a1cThreshold
-      ifcc = a1c
+      ifcc = a1c.toFixed(0)
       dcct = ifccToDcct(a1c).toFixed(1)
     else
-      dcct = a1c
+      dcct = a1c.toFixed(1)
       ifcc = dcctToIfcc(a1c).toFixed(0)

     mgdl = dcctToMgdl(dcct)
@@ -103,8 +103,9 @@
     reply = 'an a1c of ' + dcct + '% (DCCT) or '
     reply = reply + ifcc + ' mmol/mol (IFCC)'
     reply = reply + ' is about '
-    reply = reply + mgdl.toFixed(0) + 'mg/dL or '
+    reply = reply + mgdl.toFixed(0) + ' mg/dL or '
     reply = reply + mgdlToMmol(mgdl).toFixed(1) + ' mmol/L'
+
     msg.send reply

   robot.hear range, (msg) ->


charts: added console trick to disable d3 for testing

Typing in the console

  disable_d3 = true

will disable d3 for the current page - you'll have to reload the chart

--- app/assets/javascripts/lib/models/chart.coffee
+++ app/assets/javascripts/lib/models/chart.coffee
@@ -15,7 +15,7 @@
   render : =>
     return false unless @supported_by_current_browser()
     type = @get('type')
-    d3_support = Browser.hasD3Support()
+    d3_support = Browser.hasD3Support() && !window.disable_d3
     view_class = switch type
       when 'bezier'
         if d3_support


Made --beep the standard watch behaviour, and debounced it

--- command.coffee
+++ command.coffee
@@ -44,7 +44,6 @@
   ['-t', '--tokens',          'print out the tokens that the lexer/rewriter produce']
   ['-v', '--version',         'display the version number']
   ['-w', '--watch',           'watch scripts for changes and rerun commands']
-  [      '--beep',            'beep (print \\007) on compilation errors with --watch']
 ]

 # Top-level objects shared by all the functions.
@@ -137,9 +136,18 @@
   catch err
     CoffeeScript.emit 'failure', err, task
     return if CoffeeScript.listeners('failure').length
-    return printLine err.message + (if opts.beep then '\007' else '') if o.watch
+    if o.watch
+      debouncedBeep()
+      return printLine err.message
     printWarn err instanceof Error and err.stack or "ERROR: #{err}"
     process.exit 1
+
+# Beep once if there are errors in compilation of watched script(s)
+beepTimeout = null
+debouncedBeep = ->
+  clearTimeout beepTimeout
+  beepTimeout = wait 100, ->
+    process.stdout.write '\007'

 # Attach the appropriate listeners to compile scripts incoming over **stdin**,
 # and write them back to **stdout**.


title

title

--- ui-assets/index.coffee
+++ ui-assets/index.coffee
@@ -4,7 +4,7 @@
  @html ->
   @head ->
    @meta charset: "utf-8"
-   @title "Object"
+   @title "Resume sample: nearby.lk data model editor"
    @meta name: "viewport", content: "width=device-width, initial-scale=1.0"
    @meta name: "apple-mobile-web-app-capable", content:"yes"
    @link


add logging

add logging

--- services/real-time/app/coffee/DocumentUpdaterController.coffee
+++ services/real-time/app/coffee/DocumentUpdaterController.coffee
@@ -26,6 +26,7 @@
 				DocumentUpdaterController._processErrorFromDocumentUpdater(io, message.doc_id, message.error, message)

 	_applyUpdateFromDocumentUpdater: (io, doc_id, update) ->
+		logger.log({doc_id}, "apply update from doc updater")
 		for client in io.sockets.clients(doc_id)
 			if client.id == update.meta.source
 				logger.log doc_id: doc_id, version: update.v, source: update.meta?.source, "distributing update to sender"


Fix infinite scroll in conversation mode, refs #271

--- client/app/components/message-list.coffee
+++ client/app/components/message-list.coffee
@@ -300,8 +300,7 @@
                     # message count
                     # So we assume that if query.pageAfter is null, there's
                     # no more messages to display
-                    nbMessages = parseInt(@props.counterMessage, 10)
-                    if @props.messages.count() < nbMessages and
+                    if @moreToSee() and
                        @props.query.pageAfter?
                         p className: 'text-center list-footer',
                             if @props.fetching
@@ -315,6 +314,25 @@
                                     t 'list next page'
                     else
                         p ref: 'listEnd', t 'list end'
+
+
+    # Check if we should display "More messages"
+    moreToSee: ->
+        nbMessages = parseInt(@props.messagesCount, 10)
+        # when displaying conversations, we have to sum the number of messages
+        # inside each one to know if we have displayed all messages inside this
+        # box
+        if @props.settings.get('displayConversation')
+            nbInConv   = 0
+            @props.messages.map (message) =>
+                length = @props.conversationLengths.get(message.get 'conversationID')
+                if length?
+                    nbInConv += @props.conversationLengths.get(message.get 'conversationID')
+            .toJS()
+
+            return nbInConv < nbMessages
+        else
+            return @props.messages.count() < nbMessages

     refresh: (event) ->
@@ -453,8 +471,8 @@
             LayoutActionCreator.showMessageList parameters: @props.query

     _handleRealtimeGrowth: ->
-        nbMessages = parseInt @props.counterMessage, 10
-        if nbMessages < @props.messages.count() and @refs.listEnd? and
+        nbMessages = parseInt @props.messagesCount, 10
+        if (not @moreToSee()) and @refs.listEnd? and
         not DomUtils.isVisible(@refs.listEnd.getDOMNode())
             lastdate = @props.messages.last().get('date')
             SocketUtils.changeRealtimeScope @props.mailboxID, lastdate


Use recursion to substitute text

Use recursion to substitute text

--- src/atom/command-interpreter/substitution.coffee
+++ src/atom/command-interpreter/substitution.coffee
@@ -3,21 +3,29 @@
   global: false

   constructor: (@findText, @replaceText, @options) ->
-    @findRegex = new RegExp(@findText, "g")
+    @findRegex = new RegExp(@findText)
     @global = 'g' in @options

   perform: (editor) ->
-    { buffer } = editor
     selectedText = editor.getSelectedText()
-    selectionStartIndex = buffer.characterIndexForPosition(editor.getSelection().getBufferRange().start)
+    selectionStartIndex = editor.buffer.characterIndexForPosition(editor.getSelection().getBufferRange().start)
+    selectionEndIndex = selectionStartIndex + selectedText.length

-    while match = @findRegex.exec(selectedText)
-      matchStartIndex = selectionStartIndex + match.index
-      matchEndIndex = matchStartIndex + match[0].length
+    @replace(editor, selectedText, selectionStartIndex)

-      startPosition = buffer.positionForCharacterIndex(matchStartIndex)
-      endPosition = buffer.positionForCharacterIndex(matchEndIndex)
+  replace: (editor, text, startIndex) ->
+    return unless match = text.match(@findRegex)

-      buffer.change([startPosition, endPosition], @replaceText)
-      break unless @global
-      selectedText = editor.getSelectedText()
+    matchStartIndex = startIndex + match.index
+    matchEndIndex = matchStartIndex + match[0].length
+
+    buffer = editor.buffer
+    startPosition = buffer.positionForCharacterIndex(matchStartIndex)
+    endPosition = buffer.positionForCharacterIndex(matchEndIndex)
+
+    buffer.change([startPosition, endPosition], @replaceText)
+
+    if @global
+      text = text[(match.index + match[0].length)..]
+      startIndex = matchStartIndex + @replaceText.length
+      @replace(editor, text, startIndex)


Fixes

Fixes

--- src/entities.coffee
+++ src/entities.coffee
@@ -91,15 +91,6 @@
             if not testRect(pos.x+(rX-x), pos.y+(rY-y), 32, 32, me.collision.types.ALL_OBJECT, @) # Can be?
                 @pos.x = rX; @pos.y = rY
                 @updateBounds()
-    takeDamage: (amount, frames) ->
-        if @invincibleFrames <= 0
-            @health = Math.max(0, @health - amount)
-            if @health <= 0
-                shouldReset = true
-            me.audio.play('stomp')
-            @renderable.flicker(200)
-            @invincibleFrames = frames
-
     draw: (renderer) ->
         [x, y] = [@getRx(), @getRy()]
         renderer.translate(x, y)
@@ -125,11 +116,23 @@
         renderer.translate(-x, -y)
     draw: (renderer)->
         @baseDraw(renderer)
+    takeDamage: (amount, frames, playSound = false) ->
+        if @invincibleFrames <= 0
+            @health = Math.max(0, @health - amount)
+            if @health <= 0
+                @die()
+            if playSound
+                me.audio.play('stomp')
+            @renderable.flicker(200)
+            @invincibleFrames = frames
+    giveInvincibileFrames: (n) ->
+        @invincibleFrames = Math.max(@invincibleFrames, n)
     baseInit: () ->
         @alwaysUpdate = true
         @body.setMaxVelocity(MAX_SPEED, 30)
         @onPlatform = null
         @health = 100
+        @invincibleFrames = 10
     getRx: () ->
         {pos, width} = @getBounds()
         return Math.round(0.5 + pos.x + @anchorPoint.x * (width - @renderable.width))
@@ -191,10 +194,9 @@
         @renderable.setCurrentAnimation('stand')
         @firstUpdate = true
         @jumpTimer = 0
-        @invincibleFrames = 10
         @baseInit()
-    giveInvincibileFrames: (n) ->
-        @invincibleFrames = Math.max(@invincibleFrames, n)
+    die: () ->
+        shouldReset = true

     hasFloorBelow: () ->
         if wouldCollide(@,0,0, me.collision.types.WORLD_SHAPE)


add key to timezone choices

add key to timezone choices

--- src/components/course-settings/set-timezone.cjsx
+++ src/components/course-settings/set-timezone.cjsx
@@ -86,8 +86,11 @@
     {courseTimezone} = @state

     timezonesToPick = _.map timezones, (timezone) =>
+      identifier = S.dasherize(timezone)
+
       <TutorRadio
-        id={S.dasherize(timezone)}
+        id={identifier}
+        key="timezone-choice-#{identifier}"
         value={timezone}
         name={name}
         checked={timezone is courseTimezone}


Allow overriding of Flowdock endpoint.

Allow overriding of Flowdock endpoint.

--- src/flowdock.coffee
+++ src/flowdock.coffee
@@ -2,7 +2,7 @@
 querystring = require('querystring')
 events = require("events")

-host = ".flowdock.com"
+host = (process.env.FLOWDOCK_ENDPOINT || ".flowdock.com")

 class FlowdockSocket extends process.EventEmitter
   constructor: (@cookies, @clientId) ->


add checkbox to fixie

add checkbox to fixie

--- coffee/fixie.coffee
+++ coffee/fixie.coffee
@@ -305,11 +305,39 @@
     catch e
     return

+class Checkbox extends Backbone.View
+  render: =>
+    checked = @model.get(@options.property)
+    html = """
+      <form onsubmit='event.preventDefault()'>
+        <label class='fixie-checkbox'>
+          <input type='checkbox' name='#{@options.property}' #{if checked then 'checked' else ''}>
+          <span> #{@options.description or @options.property}</span>
+        </label>
+      </form>"""
+    @$el.html html
+
+    @
+
+  detectChange: =>
+    checked = @$('input').is(':checked')
+    @model.set @options.property, checked
+
+  events: =>
+    "change input[type=checkbox]": 'detectChange'
+
+  initialize: =>
+    if not @el
+        throw new Error "Couldn't find el"
+    @listenTo @model, "change:#{@options.property}", @render
+    @render()
+
 class Fixie
   @PlainTextEditor = PlainTextEditor
   @RichTextEditor = RichTextEditor
   @DateEditor = DateEditor
   @URLEditor = URLEditor
   @Preview = Preview
+  @Checkbox = Checkbox

 @Fixie = Fixie


fix: process.watchFile has moved to fs.watchFile

--- command_line.coffee
+++ command_line.coffee
@@ -100,7 +100,7 @@
 # files are updated.
 watch_scripts: ->
   watch: (source) ->
-    process.watchFile source, {persistent: true, interval: 500}, (curr, prev) ->
+    fs.watchFile source, {persistent: true, interval: 500}, (curr, prev) ->
       return if curr.mtime.getTime() is prev.mtime.getTime()
       fs.readFile source, (err, code) -> compile_script(source, code)
   watch(source) for source in sources


use Promise in Fotolife#_request

use Promise in Fotolife#_request

--- src/fotolife.coffee
+++ src/fotolife.coffee
@@ -117,19 +117,36 @@
     # TODO: check res.statusCode is 200
     @_request { method, path }, callback

-  _request: (options, callback) ->
+  _request: ({ method, path, body }, callback) ->
+    callback = callback ? (->)
     token = @_wsse.getUsernameToken @_username, @_apikey, nonceBase64: true
-    options.url = Fotolife.BASE_URL + options.path
-    options.headers =
+    params = {}
+    params.method = method
+    params.url = Fotolife.BASE_URL + path
+    params.headers =
       'Authorization': 'WSSE profile="UsernameToken"',
       'X-WSSE': 'UsernameToken ' + token
-    options.body = @_toXml(options.body) if options.body?
-    delete options.path
-    request options, (err, res) ->
-      return callback(err) if err?
-      @_toJson res.body, (err, result) ->
-        return callback(err) if err?
-        callback null, result
+    promise = if body? then @_toXml(body) else Promise.resolve(null)
+    promise
+      .then (body) =>
+        params.body = body if body?
+        @_requestPromise params
+      .then (res) =>
+        @_toJson res.body
+      .then (json) ->
+        callback(null, json)
+        json
+      .then null, (err) ->
+        callback(err)
+        err
+
+  _requestPromise: (params) ->
+    new Promise (resolve, reject) ->
+      request params, (err, res) ->
+        if err?
+          reject err
+        else
+          resolve res

   _toJson: (xml) ->
     new Promise (resolve, reject) ->


add oauth support

add oauth support

--- src/fotolife.coffee
+++ src/fotolife.coffee
@@ -1,9 +1,10 @@
+{Promise} = require 'q'
 fs = require 'fs'
 mime = require 'mime'
+oauth = require 'oauth'
 request = require 'request'
 wsse = require 'wsse'
 xml2js = require 'xml2js'
-{Promise} = require 'q'

 # Hatena::Fotolife API wrapper
 #
@@ -20,12 +21,30 @@
   # params:
   #   options: (required)
   #   - type     : authentication type. default `'wsse'`
+  #   (type 'wsse')
   #   - username : wsse authentication username. (required)
   #   - apikey   : wsse authentication apikey. (required)
-  constructor: ({ type, username, apikey }) ->
+  #   (type 'oauth')
+  #   - consumerKey       : oauth consumer key. (required)
+  #   - consumerSecret    : oauth consumer secret. (required)
+  #   - accessToken       : oauth access token. (required)
+  #   - accessTokenSecret : oauth access token secret. (required)
+  constructor: ({
+    type,
+    username,
+    apikey,
+    consumerKey
+    consumerSecret,
+    accessToken,
+    accessTokenSecret
+  }) ->
     @_type = type ? 'wsse'
     @_username = username
     @_apikey = apikey
+    @_consumerKey = consumerKey
+    @_consumerSecret = consumerSecret
+    @_accessToken = accessToken
+    @_accessTokenSecret = accessTokenSecret
     @_wsse = wsse()

   # POST PostURI (/atom/post)
@@ -145,13 +164,20 @@

   _request: ({ method, path, body, statusCode }, callback) ->
     callback = callback ? (->)
-    token = @_wsse.getUsernameToken @_username, @_apikey, nonceBase64: true
     params = {}
     params.method = method
     params.url = Fotolife.BASE_URL + path
-    params.headers =
-      'Authorization': 'WSSE profile="UsernameToken"',
-      'X-WSSE': 'UsernameToken ' + token
+    if @_type is 'wsse'
+      token = @_wsse.getUsernameToken @_username, @_apikey, nonceBase64: true
+      params.headers =
+        'Authorization': 'WSSE profile="UsernameToken"'
+        'X-WSSE': 'UsernameToken ' + token
+    else if @_type is 'oauth'
+      params.oauth =
+        consumer_key: @_consumerKey
+        consumer_secret: @_consumerSecret
+        token: @_accessToken
+        token_secret: @_accessTokenSecret
     promise = if body? then @_toXml(body) else Promise.resolve(null)
     promise
       .then (body) =>


Assertions to catch the empty keybinding rows when keymaps are disabled

--- spec/installed-package-view-spec.coffee
+++ spec/installed-package-view-spec.coffee
@@ -92,9 +92,15 @@
         expect(card.refs.keybindingToggle.checked).toBe false
         expect(_.include(atom.config.get('core.packagesWithKeymapsDisabled') ? [], 'language-test')).toBe true

+        keybindingRows = view.element.querySelector('.package-keymap-table tbody tr')
+        expect(keybindingsRows.length).toBe 1
+
         card.refs.keybindingToggle.click()
         expect(card.refs.keybindingToggle.checked).toBe true
         expect(_.include(atom.config.get('core.packagesWithKeymapsDisabled') ? [], 'language-test')).toBe false
+
+        keybindingRows = view.element.querySelector('.package-keymap-table tbody tr')
+        expect(keybindingsRows.length).toBe 1

   describe "when a keybinding is copied", ->
     [pack, card] = []


Added: proper wrapping for commonjs, amd and global scope.

--- src/aria-tips.coffee
+++ src/aria-tips.coffee
@@ -1,104 +1,112 @@
-arrowDelta = 6
+((root, factory) ->
+
+    if typeof define is "function" and define.amd
+        define [], factory
+    else if typeof module is "object" and module.exports
+        module.exports = factory()
+    else
+        root.AriaTips = factory()
+
+)(this, ->
+
+    arrowDelta = 6
+
+    oppositePosition = (position) ->
+        switch position
+            when 'top'    then 'bottom'
+            when 'bottom' then 'top'
+            when 'left'   then 'right'
+            when 'right'  then 'left'

-oppositePosition = (position) ->
-    switch position
-        when 'top'    then 'bottom'
-        when 'bottom' then 'top'
-        when 'left'   then 'right'
-        when 'right'  then 'left'
+    attachTooltips = ->
+        tips = document.querySelectorAll '[role=tooltip]'
+        for tip in tips
+            document.body.appendChild tip
+            controls = document.querySelectorAll "[aria-describedby=#{tip.id}]"
+            bindHoverEvents.call control for control in controls

-attachTooltips = ->
-    tips = document.querySelectorAll '[role=tooltip]'
-    for tip in tips
-        document.body.appendChild tip
-        controls = document.querySelectorAll "[aria-describedby=#{tip.id}]"
-        bindHoverEvents.call control for control in controls
+    positionTooltip = (control)->
+        resetTooltip.call @
+
+        direction = control.dataset.tooltipDirection or @dataset.tooltipDirection
+        boundingBox = control.getBoundingClientRect()
+        center =
+            x: 0 | boundingBox.left + (boundingBox.right - boundingBox.left) / 2
+            y: 0 | boundingBox.top + (boundingBox.bottom - boundingBox.top) / 2
+        result =
+            top:    'auto'
+            bottom: 'auto'
+            left:   'auto'
+            right:  'auto'
+
+        position = switch direction
+            when 'top'    then window.innerHeight - boundingBox.top
+            when 'bottom' then boundingBox.bottom
+            when 'left'   then window.innerWidth - boundingBox.left
+            when 'right'  then boundingBox.right
+
+        result[oppositePosition(direction)] = "#{0 | position + arrowDelta}px"
+        switch direction
+            when 'top', 'bottom' then result.left = "#{center.x}px"
+            when 'left', 'right' then result.top = "#{center.y}px"
+
+        @style[prop] = value for prop, value of result
+        unless direction is @dataset.tooltipDirection
+            @origDirection = @dataset.tooltipDirection
+            @dataset.tooltipDirection = direction

-positionTooltip = (control)->
-    resetTooltip.call @
-
-    direction = control.dataset.tooltipDirection or @dataset.tooltipDirection
-    boundingBox = control.getBoundingClientRect()
-    center =
-        x: 0 | boundingBox.left + (boundingBox.right - boundingBox.left) / 2
-        y: 0 | boundingBox.top + (boundingBox.bottom - boundingBox.top) / 2
-    result =
-        top:    'auto'
-        bottom: 'auto'
-        left:   'auto'
-        right:  'auto'
-
-    position = switch direction
-        when 'top'    then window.innerHeight - boundingBox.top
-        when 'bottom' then boundingBox.bottom
-        when 'left'   then window.innerWidth - boundingBox.left
-        when 'right'  then boundingBox.right
-
-    result[oppositePosition(direction)] = "#{0 | position + arrowDelta}px"
-    switch direction
-        when 'top', 'bottom' then result.left = "#{center.x}px"
-        when 'left', 'right' then result.top = "#{center.y}px"
-
-    @style[prop] = value for prop, value of result
-    unless direction is @dataset.tooltipDirection
-        @origDirection = @dataset.tooltipDirection
-        @dataset.tooltipDirection = direction
+    resetTooltip = ->
+        if @origDirection
+            @dataset.tooltipDirection = @origDirection
+            @origDirection = null

-resetTooltip = ->
-    if @origDirection
-        @dataset.tooltipDirection = @origDirection
-        @origDirection = null
+    fillTooltipLabel = (control) ->
+        labels = @querySelectorAll 'pre.label'
+        label.parentNode.removeChild label for label in labels
+
+        labelText = control.getAttribute('aria-label');
+        return unless labelText
+
+        label = document.createElement 'pre'
+        label.classList.add 'label'
+        label.innerHTML = "(#{labelText})"
+        @appendChild label

-fillTooltipLabel = (control) ->
-    labels = @querySelectorAll 'pre.label'
-    label.parentNode.removeChild label for label in labels
+    bindHoverEvents = ->
+        tip = document.querySelector "##{@getAttribute 'aria-describedby'}"
+        timeout = null

-    labelText = control.getAttribute('aria-label');
-    return unless labelText
+        onMouseOver = =>
+            tip.addEventListener 'mouseover', onMouseOver
+            tip.addEventListener 'mouseout', onMouseOut

-    label = document.createElement 'pre'
-    label.classList.add 'label'
-    label.innerHTML = "(#{labelText})"
-    @appendChild label
+            if timeout
+                clearTimeout timeout
+                timeout = null
+                tip.setAttribute 'aria-hidden', false
+            else
+                fillTooltipLabel.call tip, @
+                positionTooltip.call tip, @
+                timeout = setTimeout ->
+                    tip.setAttribute 'aria-hidden', false
+                , 300

+        onMouseOut = ->
+            clearTimeout timeout
+            timeout = setTimeout ->
+                timeout = null
+                tip.removeEventListener 'mouseover', onMouseOver
+                tip.removeEventListener 'mouseout', onMouseOut
+                tip.setAttribute 'aria-hidden', true
+            , 30

-bindHoverEvents = ->
-    tip = document.querySelector "##{@getAttribute 'aria-describedby'}"
-    timeout = null
+        @addEventListener 'mouseover', onMouseOver
+        @addEventListener 'mouseout', onMouseOut

-    onMouseOver = =>
-        tip.addEventListener 'mouseover', onMouseOver
-        tip.addEventListener 'mouseout', onMouseOut
-
-        if timeout
-            clearTimeout timeout
-            timeout = null
-            tip.setAttribute 'aria-hidden', false
-        else
-            fillTooltipLabel.call tip, @
-            positionTooltip.call tip, @
-            timeout = setTimeout ->
-                tip.setAttribute 'aria-hidden', false
-            , 300
-
-    onMouseOut = ->
-        clearTimeout timeout
-        timeout = setTimeout ->
-            timeout = null
-            tip.removeEventListener 'mouseover', onMouseOver
-            tip.removeEventListener 'mouseout', onMouseOut
-            tip.setAttribute 'aria-hidden', true
-        , 30
-
-    @addEventListener 'mouseover', onMouseOver
-    @addEventListener 'mouseout', onMouseOut
-
-
-module.exports =
-    bind: ->
-        attachTooltips()
+    return bind: -> attachTooltips()
+)