Apps use different ports... so we'll run two end points here.

--- coffee/webService.coffee
+++ coffee/webService.coffee
@@ -1,3 +1,22 @@
+##############################################################################################
+# Configuration section
+logPath = 'logs/'
+subscriptionDirectory = 'subscribedUsers/'
+
+apiBaseUrl = 'http://shackapi.stonedonkey.com/'
+apiParentAuthorQuery = 'Search/?ParentAuthor='
+debugMode = false
+
+#If set to true, the SSL Key and Certificate will be used and a https server will be started.
+USESSL = true
+#SSL Key and Certificate file locations.
+sslKey = 'key.key'
+sslCert = 'ssl.crt'
+caPem = 'sub.class1.server.ca.pem'
+# End Configuration section
+#############################################################################################
+
+
 sys = require 'util'
 http = require 'http'
 https = require 'https'
@@ -9,21 +28,11 @@
 winston = require 'winston'

 rootPath = __dirname
-logPath = path.join(rootPath, 'logs/')
-subscriptionDirectory = path.join(rootPath, 'subscribedUsers/')
-
-apiBaseUrl = 'http://shackapi.stonedonkey.com/'
-apiParentAuthorQuery = 'Search/?ParentAuthor='
-debugMode = false
-
-#If set to true, the SSL Key and Certificate will be used and a https server will be started.
-USESSL = true
-#SSL Key and Certificate file locations.
-sslKey = 'key.key'
-sslCert = 'ssl.crt'
-caPem = 'sub.class1.server.ca.pem'
-# End Configuration section
-#############################################################################################
+logPath = path.join(rootPath, logPath)
+subscriptionDirectory = path.join(rootPath, subscriptionDirectory)
+sslKey = path.join(rootPath, sslKey)
+sslCert = path.join(rootPath, sslCert)
+caPem = path.join(rootPath, caPem)

 logger = new (winston.Logger)(
 	transports: [
@@ -32,8 +41,8 @@
 	]
 )

-#serverPort = 12243 #production
-serverPort = 12253 #development
+serverPort2 = 12243 #Windows Phone apps use this port.
+serverPort = 12253 #Windows Store app uses this port... oops.

 #Subscribe a user, or update an existing user
 SubscribeRequest = (subResponse, userName, parsedUrl, requestData) =>
@@ -66,7 +75,7 @@

 		siteUrl = url.parse(apiBaseUrl + apiParentAuthorQuery + userName)

-		requestOptions =
+		requestOptions =
 			host: siteUrl.host,
 			port: 80,
 			path: siteUrl.path
@@ -84,15 +93,15 @@
 			)

 			res.on('end', () =>
-				try
+				try
 					#I would prefer to switch to the json api, but it appears to not show the total_results.  It's null.
 					xmlDoc = new jsxml.XML(dataReceived)

-					if (xmlDoc is null)
+					if (xmlDoc is null)
 						return

 					totalResultsAttribute = xmlDoc.attribute('total_results')
-					if (totalResultsAttribute is null)
+					if (totalResultsAttribute is null)
 						return

 					#This is the number of results we know about right this very moment.
@@ -117,7 +126,7 @@
 							logger.error("Missing notification type.")
 							subResponse.end("Missing notification type.")
 							return
-
+
 						if (parsedQuery.hasOwnProperty('deviceId'))
 							saveObject.deviceId = parsedQuery['deviceId']
 						else
@@ -125,7 +134,7 @@
 							logger.error("Missing device id.")
 							subResponse.end("Missing device id.")
 							return
-
+

 					logger.silly("Subscribing with info: " + JSON.stringify(saveObject))

@@ -160,9 +169,9 @@

 #Remove a user
 RemoveRequest = (response, parsedUrl, userName) =>
-	if (parsedUrl.hasOwnProperty('query'))
+	if (parsedUrl.hasOwnProperty('query'))
 		parsedQuery = querystring.parse(parsedUrl.query)
-		if (parsedQuery.hasOwnProperty('deviceId'))
+		if (parsedQuery.hasOwnProperty('deviceId'))
 			deviceId = parsedQuery['deviceId']
 			file = path.join(subscriptionDirectory, parsedQuery['deviceId'])
 			if(path.existsSync(file))
@@ -219,7 +228,7 @@
 				else if (request.method == 'DELETE')
 					requestHandled = true
 					RemoveRequest(response, parsedUrl, splitPath[2])
-
+
 		if(!requestHandled)
 			logger.error("Request not handled.")
 			response.writeHead(404, { "Content-Type": "text/plain" })
@@ -235,9 +244,13 @@
 	}
 	https.createServer(options, handleServer).listen(serverPort)
 	logger.info("Server running at https://localhost:" + serverPort)
+	https.createServer(options, handleServer).listen(serverPort2)
+	logger.info("Server running at https://localhost:" + serverPort2)
 else
 	http.createServer(handleServer).listen(serverPort)
 	logger.info("Server running at http://localhost:" + serverPort)
+	http.createServer(handleServer).listen(serverPort2)
+	logger.info("Server running at http://localhost:" + serverPort2)

 logger.verbose("rootPath = " + rootPath)
 logger.verbose("logPath = " + logPath)


rename file

rename file

--- grammars/innosetup.cson
+++ grammars/innosetup.cson
@@ -1,60 +0,0 @@
-fileTypes: [
-  "iss"
-  "isl"
-]
-name: "Inno Setup"
-comment: "https://github.com/idleberg/atom-language-inno"
-patterns: [
-  {
-    captures:
-      "1":
-        name: "punctuation.definition.comment.inno"
-    match: "^\\s*(;|#).*$"
-    name: "comment.line.semicolon.inno"
-  }
-  {
-    match: "\\[[\\w]+\\]"
-    name: "entity.name.section.inno"
-  }
-  {
-    match: "{(#)?[\\w]+}"
-    name: "variable.constant.inno"
-  }
-  {
-    match: "%[\\w]"
-    name: "variable.inno"
-  }
-  {
-    match: "\\b(AllowCancelDuringInstall|AllowNetworkDrive|AllowNoIcons|AllowRootDirectory|AllowUNCPath|AlwaysCreateUninstallIcon|AlwaysRestart|AlwaysShowComponentsList|AlwaysShowDirOnReadyPage|AlwaysShowGroupOnReadyPage|AlwaysUsePersonalGroup|AppComments|AppContact|AppCopyright|AppendDefaultDirName|AppendDefaultGroupName|AppId|AppModifyPath|AppMutex|AppName|AppPublisher|AppPublisherURL|AppReadmeFile|AppSupportPhone|AppSupportURL|AppUpdatesURL|AppVerName|AppVersion|ArchitecturesAllowed|ArchitecturesInstallIn64BitMode|BackColor|BackColor2|BackColorDirection|BackSolid|ChangesAssociations|ChangesEnvironment|CloseApplications|CloseApplicationsFilter|Compression|CompressionThreads|CopyrightFontName|CopyrightFontSize|CreateAppDir|CreateUninstallRegKey|DefaultDialogFontName|DefaultDirName|DefaultGroupName|DefaultUserInfoName|DefaultUserInfoOrg|DefaultUserInfoSerial|DialogFontName|DialogFontSize|DirExistsWarning|DisableDirPage|DisableFinishedPage|DisableProgramGroupPage|DisableReadyMemo|DisableReadyPage|DisableStartupPrompt|DisableWelcomePage|DiskClusterSize|DiskSliceSize|DiskSpanning|EnableDirDoesntExistWarning|Encryption|ExtraDiskSpaceRequired|FlatComponentsList|InfoAfterFile|InfoBeforeFile|InternalCompressLevel|LanguageCodePage|LanguageDetectionMethod|LanguageID|LanguageName|LicenseFile|LZMAAlgorithm|LZMABlockSize|LZMADictionarySize|LZMAMatchFinder|LZMANumBlockThreads|LZMANumFastBytes|LZMAUseSeparateProcess|MergeDuplicateFiles|MinVersion|OnlyBelowVersion|Output|OutputBaseFilename|OutputDir|OutputManifestFile|Password|PrivilegesRequired|ReserveBytes|RestartApplications|RestartIfNeededByRun|RightToLeft|SetupIconFile|SetupLogging|ShowComponentSizes|ShowLanguageDialog|ShowTasksTreeLines|ShowUndisplayableLanguages|SignedUninstaller|SignedUninstallerDir|SignTool|SignToolRetryCount|SlicesPerDisk|SolidCompression|SourceDir|TerminalServicesAware|TimeStampRounding|TimeStampsInUTC|TitleFontName|TitleFontSize|TouchDate|TouchTime|Uninstallable|UninstallDisplayIcon|UninstallDisplayName|UninstallDisplaySize|UninstallFilesDir|UninstallLogMode|UninstallRestartComputer|UpdateUninstallLogAppName|UsePreviousAppDir|UsePreviousGroup|UsePreviousLanguage|UsePreviousSetupType|UsePreviousTasks|UsePreviousUserInfo|UserInfoPage|UseSetupLdr|VersionInfoCompany|VersionInfoCopyright|VersionInfoDescription|VersionInfoProductName|VersionInfoProductTextVersion|VersionInfoProductVersion|VersionInfoTextVersion|VersionInfoVersion|WelcomeFontName|WelcomeFontSize|WindowResizable|WindowShowCaption|WindowStartMaximized|WindowVisible|WizardImageBackColor|WizardImageFile|WizardImageStretch|WizardSmallImageFile)\\b"
-    name: "keyword.command.inno"
-  }
-  {
-    match: "\\b(BeveledLabel|MyAppName|MyAppVerName|MyDescription)\\b"
-    name: "keyword.messages.inno"
-  }
-  {
-    match: "\\b(DisableAppendDir|DontMergeDuplicateFiles|MessagesFile|UninstallIconFile|UninstallIconName|UninstallStyle|WizardSmallImageBackColor|WizardStyle)\\b"
-    name: "string.obsolete.inno"
-  }
-  {
-    match: "\\b(AfterInstall|AfterMyProgInstall|BeforeInstall|BeforeMyProgInstall|Check|Components|Description|DestDir|DestName|Filename|Flags|Languages|Name|Parameters|Root|Source|StatusMsg|Subkey|Type|Types|ValueData|ValueName|ValueType|WorkingDir)\\b:"
-    name: "constant.equals.inno"
-  }
-  {
-    match: "(:)?(\\+|=)"
-    name: "constant.character.inno"
-  }
-  {
-    match: "\\b((0(x|X)[0-9a-fA-F]+)|([0-9]+(\\.[0-9]+)?))\\b"
-    name: "constant.numeric.inno"
-  }
-  {
-    match: "\\b(32bit|64bit|admin|allowunsafefiles|append|auto|binary|bzip|checkablealone|checkedonce|clAqua|clBlack|clBlue|clFuchsia|clGray|clGreen|clLime|clMaroon|clNavy|clOlive|closeonexit|clPurple|clRed|clSilver|clTeal|clWhite|clYellow|compact|comparetimestamp|compiler|confirmoverwrite|createallsubdirs|createkeyifdoesntexist|createonlyiffileexists|createvalueifdoesntexist|current|custom|deleteafterinstall|deletekey|deletevalue|desktopicon|dirifempty|disablenouninstallwarning|dontcloseonexit|dontcopy|dontcreatekey|dontinheritcheck|dontverifychecksum|dword|excludefromshowinnewinstall|exclusive|expandsz|external|fast|files|filesandordirs|fixed|foldershortcut|fontisnttruetype|full|gacinstall|help|hidewizard|ia64|ignoreversion|iscustom|isreadme|lefttoright|locale|lowest|lzma|lzma2|main|max|modify|multisz|new|no|nocompression|noencryption|noerror|none|noregerror|normal|nowait|onlyifdoesntexist|overwrite|overwritereadonly|postinstall|poweruser|preservestringtype|preventpinning|program|promptifolder|qword|read|readexec|readme|recursesubdirs|regserver|regtypelib|replacesameversion|restart|restartreplace|runascurrentuser|runasoriginaluser|runhidden|runminimized|setntfscompression|sharedfile|shellexec|skipifdoesntexist|skipifnotsilent|skipifsilent|skipifsourcedoesntexist|solidbreak|sortfilesbyextension|sortfilesbyname|string|toptobottom|touch|uilanguage|ultra|unchecked|uninsalwaysuninstall|uninsclearvalue|uninsdeleteentry|uninsdeletekey|uninsdeletekeyifempty|uninsdeletesection|uninsdeletesectionifempty|uninsdeletevalue|uninsneveruninstall|uninsnosharedfileprompt|uninsremovereadonly|uninsrestartdelete|unsetntfscompression|useapppaths|waituntilidle|waituntilterminated|x64|x86|yes|zip)\\b"
-    name: "string.flags.inno"
-  }
-  {
-    match: "\\b(aa|ab|ae|af|ak|am|an|ar|as|av|ay|az|ba|be|bg|bh|bi|bm|bn|bo|br|bs|ca|ce|ch|co|cr|cs|cu|cv|cy|da|de|dv|dz|ee|el|en|eo|es|et|eu|fa|ff|fi|fj|fo|fr|fy|ga|gd|gl|gn|gu|gv|ha|he|hi|ho|hr|ht|hu|hy|hz|ia|id|ie|ig|ii|ik|io|is|it|iu|ja|jv|ka|kg|ki|kj|kk|kl|km|kn|ko|kr|ks|ku|kv|kw|ky|la|lb|lg|li|ln|lo|lt|lu|lv|mg|mh|mi|mk|mk|ml|mn|mr|ms|mt|my|na|nb|nd|ne|ng|nl|nn|no|nr|nv|ny|oc|oj|om|or|os|pa|pi|pl|ps|pt|qt|qu|rg|rm|rn|ro|ru|rw|sa|sc|sd|se|sg|si|sk|sl|sm|sn|so|sq|sr|ss|st|su|sv|sw|ta|te|tg|th|ti|tk|tl|tn|to|tr|ts|tt|tw|ty|ug|uk|ur|uz|ve|vi|vo|wa|wo|xh|yi|yo|zh)(?:\\.\\w)?\\b"
-    name: "variable.language.inno"
-  }
-]
-scopeName: "source.inno"


can has stacktraces plz

can has stacktraces plz

--- src/quest.coffee
+++ src/quest.coffee
@@ -98,6 +98,7 @@
         @emit 'connectionError', err
         console.error "Couldn't connect!".red.bold
         console.error err.message.red
+        console.error err.stack.red
       else
         @emit "adventureStart"
         Sync (=> @adventure()), (err, result) =>
@@ -106,7 +107,9 @@
           if err or @silentErrors
             @emit 'adventureError', err
             console.error "Errors occurred!".red.bold
-            console.error err.message.red.underline if err
+            if err
+              console.error err.message.red.underline
+              console.error err.stack.red
             console.error()
             process.exit 1

@@ -225,6 +228,7 @@
         match = (regex) -> e.message.match regex
         if not opts.okErrors or opts.okErrors.some(match)
           console.error "Error occurred: #{e.message}".red.underline
+          console.error e.stack.red
           console.log "Retrying in #{opts.wait}ms.".red.bold
           console.log "Retries remaining: #{opts.times}".red.bold
           Sync.sleep(opts.wait)
@@ -238,6 +242,7 @@
     if opts.silent
       @silentErrors = true
       console.error error.message.red.underline
+      console.error error.stack
       return
     else
       throw error


No clue what I was doing here. This breaks when data keys are camelcased

--- src/index.cjsx
+++ src/index.cjsx
@@ -30,7 +30,7 @@
         # more elements around instead of destroying and recreating.
         # Test this with a 10000 row table when working on sorting.
         if typeof column is "string"
-          datum = deep(rowData, column.toLowerCase())
+          datum = deep(rowData, column)
           key = i + "-" + column
         else if column.path?
           datum = deep(rowData, column.path)


Create a lookup table for channel names

Create a lookup table for channel names

author.room is a special property that is copied by the Response. To ensure
any long-held Response continues to send to the correct room, a lookup table
must be used instead of adding a field onto user.

--- src/slack.coffee
+++ src/slack.coffee
@@ -2,6 +2,11 @@
 https = require 'https'

 class Slack extends Adapter
+  constructor: (robot) ->
+    super robot
+    @channelMapping = {}
+
+
   ###################################################################
   # Slightly abstract logging, primarily so that it can
   # be easily altered for unit tests.
@@ -94,7 +99,7 @@
     #
     # Note: Slack's API here uses rooom ID's, not room names. They look
     # something like C0capitallettersandnumbershere
-    params.reply_to ||= params.room
+    params.reply_to ||= @channelMapping[params.room]

     params

@@ -145,7 +150,8 @@
       author = self.getAuthorFromRequest req
       author = self.robot.brain.userForId author.id, author
       author.room = req.param 'channel_name'
-      author.reply_to = req.param 'channel_id'
+      author.reply_to = undefined
+      self.channelMapping[author.room] = req.param 'channel_id'

       if hubotMsg and author
         # Pass to the robot


Revert "added debug info"

Revert "added debug info"

This reverts commit 6489282311f3fd8f62f315a4dc7975eaa37bad1e.

--- src/slack.coffee
+++ src/slack.coffee
@@ -191,9 +191,7 @@

   send: (envelope, messages...) ->
     channel = @client.getChannelGroupOrDMByName envelope.room
-    console.log('channel (by name):', channel)
     channel = @client.getChannelGroupOrDMByID(envelope.room) unless channel
-    console.log('channel (by id):', channel)

     if not channel and @client.getUserByName(envelope.room)
       user_id = @client.getUserByName(envelope.room).id


fix #74

fix #74

--- src/server/models/RebuildCache.coffee
+++ src/server/models/RebuildCache.coffee
@@ -71,22 +71,27 @@
       )
       top10 = if commonStats.length >= 10 then commonStats[0..10] else commonStats

-      fs.writeFile("#{__dirname}/../../static/json/players.json", JSON.stringify(names), (err)->
+      fs.writeFile("#{__dirname}/../../static/json/common_stat_responce.json", JSON.stringify(commonStats), (err)->
         if err
           done(err, null)
         else
-          fs.writeFile("#{__dirname}/../../static/json/common_stat_responce.json", JSON.stringify(commonStats), (err)->
+          fs.writeFile("#{__dirname}/../../static/json/top10.json", JSON.stringify(top10), (err)->
             if err
               done(err, null)
             else
-              fs.writeFile("#{__dirname}/../../static/json/top10.json", JSON.stringify(top10), (err)->
-                if err
-                  done(err, null)
-                else
-                  done(null, top10)
+              db.Player.all().success((players)->
+                fs.writeFile("#{__dirname}/../../static/json/players.json",
+                  JSON.stringify((player.name for player in players)), (err)->
+                  if err
+                    done(err, null)
+                  else
+                    done(null, top10)
+                )
+              ).error((err)->
+                done(err, null)
               )
           )
-      )
+        )
     ).error((err)->
       done(err, null)
     )


GET /api/identity -> GET /identity

GET /api/identity -> GET /identity

It's not going to follow the standard /api rules.

--- src/server/routes/get/identity.coffee
+++ src/server/routes/get/identity.coffee
@@ -2,7 +2,7 @@

 route =
 	verb: "get"
-	path: "/api/identity"
+	path: "/identity"
 	fn: [
 		passport.authenticate "bearer", { session: false }
 		(req, res) ->


console server; prefer .js files is available.

--- src/smc-project/console_server.coffee
+++ src/smc-project/console_server.coffee
@@ -124,8 +124,16 @@
     async.series([
         (cb) ->
             # Fork off a child process that does all further work to
-            # handle a connection.
-            child = child_process.fork(__dirname + '/console_server_child.coffee', [])
+            # handle a connection.  We prefer the .js file if it is there,
+            # e.g., in kucalc, but can also work purely with .coffee, e.g.,
+            # for smc-in-smc development.  NOTE: things will break if
+            # just console_server_child.js exists, but not all other .js files.
+            module = __dirname + '/console_server_child'
+            if fs.existsSync(module + '.js')
+                module += '.js'
+            else
+                module += '.coffee'
+            child = child_process.fork(module, [])

             # Send the pid of the child to the client (the connected hub)
             socket.write_mesg('json', message.session_description(pid:child.pid))


Update tests to take into account meteor bundle program.json too

--- tests/CLITest.coffee
+++ tests/CLITest.coffee
@@ -32,9 +32,6 @@
     # We're cloning because rc mutates it's objects

-  afterEach ->
-
-
   it 'registerCommand - should have hello-world and echo registered', ->
     expect(cli.registeredCommands['hello-world']).to.be.an 'object'
     expect(cli.registeredCommands['hello-world']).to.be.to.have.keys ['func', 'defaultOptions']
@@ -52,19 +49,19 @@

   it 'executeCommand - should execute the hello-world command', ->
-    process.argv = ['node', 'main.js', 'hello-world']
+    process.argv = ['node', 'main.js', 'program-json', 'hello-world']
     cli.executeCommand()
     chai.assert logSpy.calledWith "Hello world from spacejamio:cli"

   it 'executeCommand - should execute the echo command with the default string', ->
-    process.argv = ['node', 'main.js', 'echo']
+    process.argv = ['node', 'main.js', 'program-json', 'echo']
     cli.executeCommand()
     chai.assert logSpy.calledWith "I am echoing the --string default"

   it 'executeCommand - should execute the echo command with a provided string', ->
-    process.argv = ['node', 'main.js', 'echo', '--string', 'I am echoing this string']
+    process.argv = ['node', 'main.js', 'program-json', 'echo', '--string', 'I am echoing this string']
     cli.executeCommand()
     chai.assert logSpy.calledWith "I am echoing this string"

@@ -76,14 +73,15 @@
     chai.assert logSpy.calledWith "Hello world from spacejamio:cli"
     expect(process.argv[0]).to.equal 'node'
     expect(process.argv[1]).to.equal 'main.js'
-    expect(process.argv[2]).to.equal 'hello-world'
+    expect(process.argv[2]).to.equal 'program.json'
+    expect(process.argv[3]).to.equal 'hello-world'

   it 'executeCommand - should fail if no command was provided', ->
-    process.argv = ['node', 'main.js']
+    process.argv = ['node', 'main.js', 'program-json']
     expect(CLI.executeCommand).to.throw(Error)

   it 'executeCommand - should fail if command is not registered', ->
-    process.argv = ['node', 'main.js', 'not-registered']
+    process.argv = ['node', 'main.js', 'program-json', 'not-registered']
     expect(CLI.executeCommand).to.throw(Error)


removed empty attributes & children on node

--- lib/DOM.coffee
+++ lib/DOM.coffee
@@ -37,12 +37,14 @@
     return """<#{tag}#{_renderAttr(attributes)}>#{_renderChildren(children)}</#{tag}>"""

   $.append = (parent,child) ->
+    parent.children = [] if !parent.children?
     parent.children.push child

   $.prepend = (parent,child) ->
+    parent.children = [] if !parent.children?
     parent.children.splice 0, 0, child

-  $.mergeattributes = (attributes1,attributes2) ->
+  $.mergeattributes = (attributes1={},attributes2={}) ->
     # merge shared key values where value is same type, preferring attributes1, otherwise fallback to attributes2
     attributes = {}
     for key, val of attributes1
@@ -55,6 +57,7 @@
         else if (typeof v1 is 'string') and (typeof v2 is 'string')
           attributes[key] += v1 + " " + v2
         else if (typeof v1 is 'object') and (typeof v2 is 'object')
+          # TODO: not clone
           # clone to not disrupt $h!t up the closures
           v2 = JSON.parse JSON.stringify v2
           # prefer styles from attributes1
@@ -77,21 +80,24 @@

     constructor: (tag,attributes,children)->
       tag or tag='div'
-      attributes or attributes={}
-      children or children=[]
+      #attributes or attributes={}
+      #children or children=[]

       @tag = tag
-      @attributes = attributes
-      @attributes.class or @attributes.class = []
+
+      if attributes
+
+        @attributes = attributes
+        #@attributes.class or @attributes.class = []

-      if attributes.children
+      if attributes?.children
         children = attributes.children
         delete attributes.children

       else if children? and !(children instanceof Array)
         children = [children]

-      @children = children
+      @children = children if children

       @

@@ -120,6 +126,7 @@

   _renderAttr = (o) ->
     attributes = ''
+    return attributes if !o
     for key, val of o
       continue unless notAttr.indexOf(key) is -1
       if key is 'style'


add ssl proof endpoint at /ssl

add ssl proof endpoint at /ssl

--- lib/app.coffee
+++ lib/app.coffee
@@ -25,6 +25,13 @@
   marked readme, (err, content) ->
     throw err if err
     res.send layout.replace("{{content}}", content)
+
+app.get '/ssl', (req, res, next) ->
+  res.type 'text'
+  res.send """
+    "Demonstration of domain control for DigiCert order #00462258"
+    "Please send the approval email to: ops@heroku.com"
+  """

 app.get '/node', (req, res, next) ->
   res.type 'text'


Take a copy of listeners() in overshadowListeners.

Node.js 0.7 apparently gives us a reference, causing infinite recursion.

--- src/utils.coffee
+++ src/utils.coffee
@@ -84,7 +84,10 @@
     return dst

 exports.overshadowListeners = (ee, event, handler) ->
-    old_listeners = ee.listeners(event)
+    # listeners() returns a reference to the internal array of EventEmitter.
+    # Make a copy, because we're about the replace the actual listeners.
+    old_listeners = ee.listeners(event).slice(0)
+
     ee.removeAllListeners(event)
     new_handler = () ->
         if handler.apply(this, arguments) isnt true


fix options parsing

fix options parsing

--- lib/cli.coffee
+++ lib/cli.coffee
@@ -16,18 +16,17 @@
   run: ->
     argv = optimist
       .usage('mongoose [options] <mongo url>')
-      .options(
-        schemas:
-          alias: '-s'
-          describe: 'Path to a module that exports schema definitions'
-        host:
-          alias: '-h'
-          describe: 'Host to connect to'
-        version:
-          alias: '-v'
-          describe: 'List version'
-      ).argv
-    if argv.v
+      .options 'schemas',
+        alias: 's'
+        describe: 'Path to a module that exports schema definitions'
+      .options 'host',
+        alias: 'h'
+        describe: 'Host to connect to'
+      .options 'version',
+        alias: 'v'
+        describe: 'List version'
+      .argv
+    if argv.version
       console.log require("#{__dirname}/../package.json").version
     else
-      repl.run schemas(argv.s), mongo_uri(argv.h, argv._?[0])
+      repl.run schemas(argv.schemas), mongo_uri(argv.host, argv._?[0])


limit calls to 15

limit calls to 15

--- lib/cli.coffee
+++ lib/cli.coffee
@@ -247,6 +247,7 @@
     LAST_SCRIPT_LOADED = 0
     SCRIPT_LOAD_TIME = 0
     SECONDS_ELAPSED = 0
+    RATE_LIMIT = 15
     DOMAIN = args[0]
     debug_mode = true if args[1] is 'debug'
     log 'DEBUG MODE' if debug_mode
@@ -376,7 +377,7 @@
           if debug_mode
             async.eachSeries domains, domain_iterator, -> done null, SITES
           else
-            async.each domains, domain_iterator, -> done null, SITES
+            async.eachLimit domains, RATE_LIMIT, domain_iterator, -> done null, SITES

         async.waterfall [
           (next) -> getDomains next
@@ -439,7 +440,7 @@
         if debug_mode
           async.eachSeries sites, site_iterator, -> done null, SCRIPTS
         else
-          async.each sites, site_iterator, -> done null, []
+          async.eachLimit sites, RATE_LIMIT, site_iterator, -> done null, []

       # create URLs for package/main.js
@@ -488,7 +489,7 @@
         if debug_mode
           async.eachSeries scripts, script_iterator, done
         else
-          async.each scripts, script_iterator, done
+          async.eachLimit scripts, RATE_LIMIT, script_iterator, done

       # format errors to github markup, copy to clipboard
       compileErrors = (next) ->


hub: fix something that caused a traceback in the logs

--- salvus/hub.coffee
+++ salvus/hub.coffee
@@ -706,9 +706,10 @@
         @compute_session_uuids = []
         c = clients[@conn.id]
         delete clients[@conn.id]
-        for id,f of c.call_callbacks
-            f("connection closed")
-        delete c.call_callbacks
+        if c.call_callbacks?
+            for id,f of c.call_callbacks
+                f("connection closed")
+            delete c.call_callbacks

     remember_me_failed: (reason) =>
         @push_to_client(message.remember_me_failed(reason:reason))


fixed typo in MATH

fixed typo in MATH

--- src/number_extensions.coffee
+++ src/number_extensions.coffee
@@ -267,7 +267,7 @@
     target -= Math.TAU

   while target < -Math.PI
-    target += MATH.TAU
+    target += Math.TAU

   return target


CUI: buttons: add class attribute to be able to add an extra css class to any button

--- src/elements/Button/Button.coffee
+++ src/elements/Button/Button.coffee
@@ -122,6 +122,9 @@
 		if @_primary
 			@addClass("cui-button--primary")

+		if @_class
+			@addClass(@_class)
+
 		if @_center
 			@append(@_center, "center")
 		else if @_text
@@ -467,6 +470,8 @@
 				default: false
 				check: (v) ->
 					CUI.util.isBoolean(v) or CUI.util.isFunction(v)
+			class:
+				check: String
 			active_css_class:
 				default: CUI.defaults.class.Button.defaults.active_css_class
 				check: String


Use fat arrow for stream callback

Use fat arrow for stream callback

--- src/package-converter.coffee
+++ src/package-converter.coffee
@@ -48,7 +48,7 @@
   downloadBundle: (callback) ->
     tempPath = temp.mkdirSync('atom-bundle-')
     requestOptions = url: @getDownloadUrl()
-    request.createReadStream requestOptions, (readStream) ->
+    request.createReadStream requestOptions, (readStream) =>
       readStream.on 'response', ({headers, statusCode}) ->
         if statusCode isnt 200
           callback("Download failed (#{headers.status})")


removes padding

removes padding

--- Upcoming-iCal-Events.widget/main.coffee
+++ Upcoming-iCal-Events.widget/main.coffee
@@ -58,7 +58,6 @@
     #head
         font-weight: bold
         font-size 20px
-        padding-bottom 6px

     #subhead
         font-weight: bold


force setting humanDuration

force setting humanDuration

--- src/javascripts/player.coffee
+++ src/javascripts/player.coffee
@@ -105,7 +105,7 @@
     interval = window.setInterval ((t) =>
       return unless @media.readyState > 0
       @app.episode.duration ?= @media.duration
-      @app.episode.humanDuration ?= Utils.secondsToHHMMSS(_.clone(@media.duration))
+      @app.episode.humanDuration = Utils.secondsToHHMMSS(_.clone(@media.duration))
       clear()
     ), 500


add test for transaction on polymorphic has many

--- tests/batman/model/transaction_test.coffee
+++ tests/batman/model/transaction_test.coffee
@@ -1,6 +1,9 @@
+#= require associations/polymorphic_association_helper
+
 QUnit.module "Batman.Model::transaction",
   setup: ->
     scope = this
+    window.PolymorphicAssociationHelpers.baseSetup.apply(scope)
     class @TestNested extends Batman.Model
       @resourceName: 'testNested'
       @persist Batman.RestStorage
@@ -204,3 +207,24 @@
   equal @base.get('banana'), undefined
   equal @nested.get('name'), 'bob'
   equal @apple1.get('name'), 'apple1'
+
+asyncTest 'polymorphic association sets get transactions', 8, ->
+  @Store.find 1, (err, store) =>
+    throw err if err
+    store.get('metafields')
+    delay =>
+      transaction = store.transaction()
+      metafieldsTransaction = transaction.get('metafields')
+      ok metafieldsTransaction.get('association').constructor == Batman.PolymorphicHasManyAssociation, 'its actually polymorphic'
+      ok metafieldsTransaction.isTransaction, 'Its actually a transaction'
+      metafieldsTransaction.add(new @Metafield)
+      ok metafieldsTransaction.get('length'), 3
+      ok store.get('metafields.length'), 2, 'New items arent added'
+
+      metafieldsTransaction.get('first').set('key', 'Transaction metafield')
+      ok metafieldsTransaction.get('first.key') == 'Transaction metafield'
+      ok store.get('metafields.first.key') != "Transaction metafield", 'item changes arent made'
+
+      metafieldsTransaction.applyChanges()
+      ok store.get('metafields.length'), 3, 'new items are added'
+      ok store.get('metafields.first.key') == "Transaction metafield", 'item changes are applied'


Do no fix ESLint errors on save.

Do no fix ESLint errors on save.

This was causing an issue with trying to perform debugging.

--- configs/atom/config.cson
+++ configs/atom/config.cson
@@ -57,7 +57,6 @@
     disableFSCache: true
     disableWhenNoEslintConfig: false
     eslintrcPath: "/Users/koop/.control/configs/eslintrc.js"
-    fixOnSave: true
   "linter-jscs":
     preset: "wordpress"
   "linter-jslint":


IMPROVEMENT: dirtDrawAreas (plural!)

IMPROVEMENT: dirtDrawAreas (plural!)

--- source/Art/Engine/Core/CanvasElement.coffee
+++ source/Art/Engine/Core/CanvasElement.coffee
@@ -79,7 +79,7 @@

     @_attach @_getOrCreateCanvasElement options
     @engineStat = new EngineStat
-    @_dirtyDrawArea = null
+    @_dirtyDrawAreas = null

     @pointerEventManager = new PointerEventManager canvasElement:@
     self.canvasElement ||= @
@@ -194,7 +194,6 @@

     false

-  # eventually this will only mark an area as needing drawing rather than the whole screen.
   # NOTE: For geometry changes, this gets called twice for the same element:
   #   once before and once after it "moves"
   #   As such, if we are invalidating rectangular areas, we need to do it immediately with each call.
@@ -205,9 +204,17 @@
     else
       @drawArea

-    # descendant && log _needsRedrawing: {descendant: descendant.class, dirtyArea}
-
-    @_dirtyDrawArea = dirtyArea.unionInto @_dirtyDrawArea
+    if @_dirtyDrawAreas
+      foundOverlap = false
+      while foundOverlap
+        foundOverlap = false
+        for area, i in @_dirtyDrawAreas when area.overlaps dirtyArea
+          foundOverlap = true
+          area.unionInto dirtyArea
+          @_dirtyDrawAreas = arrayWithout @_dirtyDrawAreas, i
+      @_dirtyDrawAreas.push dirtyArea
+    else
+      @_dirtyDrawAreas = [dirtyArea]

     super
     @queueDrawEpoch()
@@ -530,7 +537,6 @@
   ###############################################

   draw: ->
-    # log draw: clone {@_dirtyDrawArea}

     Element.resetStats()
     frameStartTime = currentSecond()
@@ -541,16 +547,18 @@
       @engineStat.add "frameTimeMS", (frameStartTime - @lastFrameTime) * 1000
     @lastFrameTime = frameStartTime

-    # draw
-    @canvasBitmap?.clippedTo @_dirtyDrawArea || @drawArea, =>
-      super @canvasBitmap, @elementToParentMatrix
-
-    # @_dirtyDrawArea && @canvasBitmap?.drawBorder null, @_dirtyDrawArea, color: "red"
+    for dirtyDrawArea in @_dirtyDrawAreas || [@drawArea]
+      # draw
+      @canvasBitmap?.clippedTo dirtyDrawArea, =>
+        super @canvasBitmap, @elementToParentMatrix
+
+    # for dirtyDrawArea in @_dirtyDrawAreas || [@drawArea]
+    #   @canvasBitmap?.drawBorder null, dirtyDrawArea, color: "red"

     frameEndTime = currentSecond()
     @engineStat.add "drawTimeMS", (frameEndTime - frameStartTime) * 1000 | 0

-    @_dirtyDrawArea = null
+    @_dirtyDrawAreas = null
     # @_showDrawStats()

   _showDrawStats: ->


Max zoom.

Max zoom.

--- src/views.coffee
+++ src/views.coffee
@@ -1,6 +1,7 @@
 define 'app/views', ['underscore', 'backbone', 'backbone.marionette', 'leaflet', 'i18next', 'TweenLite', 'app/p13n', 'app/widgets', 'app/jade', 'app/models', 'app/search', 'app/color'], (_, Backbone, Marionette, Leaflet, i18n, TweenLite, p13n, widgets, jade, models, search, colors) ->

     PAGE_SIZE = 200
+    MAX_AUTO_ZOOM = 12

     class SMItemView extends Marionette.ItemView
         templateHelpers:
@@ -120,9 +121,9 @@
         effective_center: ->
             [ Math.round(@effective_horizontal_center()),
               Math.round(@map_view.height() / 2) ]
-        effective_padding: ->
+        effective_padding_top_left: (pad) ->
             sidebar_edge = @service_sidebar.right_edge_coordinate()
-            [sidebar_edge, 100]
+            [sidebar_edge, pad]

         add_service_points: (service, spinner_target = null) ->
             unit_list = new models.UnitList pageSize: PAGE_SIZE
@@ -202,9 +203,10 @@
             map = @get_map()
             marker_bounds = @map_markers().getBounds()
             unless map.getBounds().intersects marker_bounds
-                map.fitBounds marker_bounds,
-                    paddingTopLeft: @effective_padding()
-
+                opts =
+                    paddingTopLeft: @effective_padding_top_left(100)
+                    maxZoom: MAX_AUTO_ZOOM
+                map.fitBounds marker_bounds, opts

         # The transitions triggered by removing the class landing from body are defined
         # in the file landing-page.less.


Fix max height for position details.

Fix max height for position details.

--- src/views.coffee
+++ src/views.coffee
@@ -1103,7 +1103,7 @@

     class PositionDetailsView extends SMLayout
         id: 'details-view-container'
-        className: 'navigation-element'
+        className: 'navigation-element limit-max-height'
         template: 'position'
         regions:
             'area_services': '.area-services-placeholder'


refactor charts.coffee

refactor charts.coffee

--- app/assets/javascripts/app/chart.coffee
+++ app/assets/javascripts/app/chart.coffee
@@ -20,7 +20,7 @@
         ]
     series

-  generateMonthCategories: (labels) ->
+  generateCategories: (labels) ->
     categories = []
     for month, i in labels
       date = moment(month)
@@ -37,16 +37,15 @@
         break
     currentWeek

-  invoicesChart: (id, data) ->
+  baseChartOptions: (id, data) ->
     currentWeek = @getCurrentWeek(data.labels)
-    $(id).highcharts
+    {
       chart:
         type: 'line'
       credits:
         enabled: false
       title:
         text: null
-        useHTML: true
       noData:
         style:
           fontSize: "18px"
@@ -59,30 +58,26 @@
               mouseOver: ->
                 $("#{id} .highcharts-xaxis-labels span").removeClass('hover')
                 $("#{id} .highcharts-xaxis-labels span:contains(#{@category.short})").addClass('hover')
-      xAxis: [{
-        categories: @generateMonthCategories(data.labels),
-        crosshair: true,
+      xAxis:
+        categories: @generateCategories(data.labels)
         title:
           text: null
         labels:
           format: '{value.short}'
           useHTML: true
         plotBands: [{
-          color: 'rgba(66, 139, 202, 0.2)'
+          color: 'rgba(155, 200, 255, 0.2)'
           from: currentWeek.start
           to: currentWeek.end
         }]
-      }],
       yAxis:
         startOnTick: false
-        labels:
-          format: '{value}k €'
-          formatter: ->
-            value = if @value < 1000 then @value else "#{@value / 1000.0}k"
-            "#{value} €"
         title:
           text: null
       tooltip:
+        crosshairs: [{
+          color: 'rgba(200, 200, 200, 0.2)'
+        }]
         shared: true
         headerFormat: '
{point.key.long}
'
         pointFormatter: ->
@@ -94,110 +89,66 @@
           enabled: false
       legend:
         enabled: false
-        useHTML: true
-        verticalAlign: 'top'
       series: @generateSeries(data.datasets)
+    }
+
+  invoicesChart: (id, data) ->
+    options = @baseChartOptions(id, data)
+    options.yAxis['labels'] =
+      format: '{value}k €'
+      formatter: ->
+        value = if @value < 1000 then @value else "#{@value / 1000.0}k"
+        "#{value} €"
+
+    $(id).highcharts(options)

   budgetChart: (id, data) ->
-    currentWeek = @getCurrentWeek(data.labels)
-    $(id).highcharts
-      chart:
-        type: 'line'
-        events:
-          load: (e) ->
-            return unless data.datasets[0]
-            segments = []
-            width = @plotWidth / @pointCount
-            lastPosition = -1
-            for position in @xAxis[0].tickPositions
-              segmentPosition = @plotLeft
-              segmentWidth = 0
-              for i in [0..@pointCount - 1]
-                if i <= position
-                  segmentPosition += width
-                  if i > lastPosition
-                    segmentWidth += width
+    options = @baseChartOptions(id, data)
+    options.chart['events'] =
+      load: (e) ->
+        return unless data.datasets[0]
+        segments = []
+        width = @plotWidth / @pointCount
+        lastPosition = -1
+        for position in @xAxis[0].tickPositions
+          segmentPosition = @plotLeft
+          segmentWidth = 0
+          for i in [0..@pointCount - 1]
+            if i <= position
+              segmentPosition += width
+              if i > lastPosition
+                segmentWidth += width

-              segments.push
-                position: segmentPosition
-                width: segmentWidth
+          segments.push
+            position: segmentPosition
+            width: segmentWidth

-              lastPosition = position
+          lastPosition = position

-            for segment, i in segments
-              $label = $($(id).find('.highcharts-xaxis-labels span')[i])
-              $label.css('left', segment.position - (segment.width / 2) - ($label.width() / 2) - 1)
+        for segment, i in segments
+          $label = $($(id).find('.highcharts-xaxis-labels span')[i])
+          $label.css('left', segment.position - (segment.width / 2) - ($label.width() / 2) - 1)
+    options.xAxis['tickPositions'] = data.ticks
+    options.yAxis['min'] = if data.datasets[0] && parseInt(data.datasets[0].data[0], 10) is 0 then undefined else 0
+    options.yAxis.startOnTick = false
+    options.yAxis['labels'] =
+      useHTML: true
+      format: '{value}k €'
+      formatter: ->
+        value = if @value < 1000 then @value else "#{@value / 1000.0}k"
+        "#{value} €"
+    options.yAxis['plotLines'] = [{
+      value: data.budget
+      color: '#777777'
+      width: 2
+      label:
+        useHTML: true
+        style:
+          fontSize: null
+        text: "#{I18n.t("labels.chart.project.budget_estimate")}: #{accounting.formatMoney(data.budget, {symbol: '€', format: '%v %s', decimal: ',', thousand: '.'})}"
+    }]

-      credits:
-        enabled: false
-      title:
-        text: null
-        useHTML: true
-      noData:
-        style:
-          fontSize: "18px"
-          fontWeight: "bold"
-          color: "#ccc"
-      plotOptions:
-        series:
-          point:
-            events:
-              mouseOver: ->
-                $("#{id} .highcharts-xaxis-labels span").removeClass('hover')
-                $("#{id} .highcharts-xaxis-labels span:contains(#{@category.short})").addClass('hover')
-      xAxis: [{
-        categories: @generateMonthCategories(data.labels),
-        title:
-          text: null
-        labels:
-          format: '{value.short}'
-          useHTML: true
-        plotBands: [{
-          color: 'rgba(66, 139, 202, 0.2)'
-          from: currentWeek.start
-          to: currentWeek.end
-        }]
-        tickPositions: data.ticks
-      }],
-      yAxis:
-        min: if data.datasets[0] && parseInt(data.datasets[0].data[0], 10) is 0 then undefined else 0
-        startOnTick: false
-        labels:
-          useHTML: true
-          format: '{value}k €'
-          formatter: ->
-            value = if @value < 1000 then @value else "#{@value / 1000.0}k"
-            "#{value} €"
-        title:
-          text: null
-        plotLines: [{
-          value: data.budget
-          color: '#777777'
-          width: 2
-          label:
-            useHTML: true
-            style:
-              fontSize: null
-            text: "#{I18n.t("labels.chart.project.budget_estimate")}: #{accounting.formatMoney(data.budget, {symbol: '€', format: '%v %s', decimal: ',', thousand: '.'})}"
-        }]
-      tooltip:
-        shared: true
-        crosshairs: [{
-          color: 'rgba(51, 51, 51, 0.2)'
-        }]
-        headerFormat: '
{point.key.date}
'
-        pointFormatter: ->
-          value = accounting.formatMoney(@y, {symbol: '€', format: '%v %s', decimal: ',', thousand: '.'})
-          "

\u25CF #{@series.name}:

#{value}

"
-        useHTML: true
-      navigation:
-        buttonOptions:
-          enabled: false
-      legend:
-        enabled: false
-        useHTML: true
-        verticalAlign: 'top'
-      series: @generateSeries(data.datasets)
+    $(id).highcharts(options)

 $(document).on 'mouseleave', '.chart', ->
   $(@).find('.highcharts-xaxis-labels span').removeClass('hover')


use current date when opening add time dialogue fixes #5964

--- app/assets/javascripts/layout.js.coffee
+++ app/assets/javascripts/layout.js.coffee
@@ -4,6 +4,7 @@
     opened: (e) ->
       if e.target.id == 'add-time-modal'
         $('#time_entry_from_time').val(moment().format('HH:mm'))
+        $("#time_entry_start_date").val(moment().format('YYYY-MM-DD'))
   })
   .foundation('tooltips')


better form reset after post creation

better form reset after post creation

--- app/assets/javascripts/models.js.coffee
+++ app/assets/javascripts/models.js.coffee
@@ -53,7 +53,7 @@

     @after_create: (post) ->
       new PostView post
-      $('#new_post textarea').text('')
+      $('#new_post form')[0].reset()

     @after_update: (post)->
       new PostEditedView post


Remove old unused function (refactor only)

--- app/assets/javascripts/static.js.coffee
+++ app/assets/javascripts/static.js.coffee
@@ -1,9 +1,6 @@
 # Place all the behaviors and hooks related to the matching controller here.
 # All this logic will automatically be available in application.js.
 # You can use CoffeeScript in this file: http://coffeescript.org/
-
-roundUpBy = (value, round_to) ->
-  return round_to * Math.ceil(value / round_to)

 formatNumber = (number) ->
   Math.ceil(number).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",")


testing ssh

testing ssh

--- app/coffeescripts/LayersMenuView.coffee
+++ app/coffeescripts/LayersMenuView.coffee
@@ -26,7 +26,7 @@
     $("##{@options.parentId}").append(@el)
     @

-  #these open and close the Layers Menu itself
+  #these open and close the Layers Menu itself
   displayOff: (e) =>
     @$el.removeClass "open"


Forgot to rename the model where it's used

--- resources/assets/lib/modding-profile/events.coffee
+++ resources/assets/lib/modding-profile/events.coffee
@@ -17,7 +17,7 @@
           div className: 'modding-profile-list__empty', osu.trans('users.show.extra.none')
         else
           el React.Fragment, null,
-            el DiscussionEvents,
+            el BeatmapsetEvents,
               events: @props.events
               mode: 'profile'
               users: @props.users


Fixed postrender.

Fixed postrender.

Also aligned the non-postrender case with the postrender case, this should better take care of corner cases.

--- src/zappa.coffee
+++ src/zappa.coffee
@@ -338,7 +338,11 @@

         render = (name,opts = {},fn) ->

-          report = fn ? next
+          report = fn ? (err,html) ->
+            if err
+              next err
+            else
+              res.send html

           # Make sure the second arg is an object.
           if typeof opts is 'function'
@@ -349,7 +353,7 @@
             opts.params = data

           if not opts.postrender?
-            postrender = fn
+            postrender = report
           else
             postrender = (err, str) ->
               if err then return report err


In some cases (I suppose when these messages might be broadcast, but why?) the `ack` callback is not provided.

--- src/zappa.coffee
+++ src/zappa.coffee
@@ -518,6 +518,7 @@
       ws_handlers.disconnect.apply(ctx) if ws_handlers.disconnect?

     socket.on '__zappa_key', (data,ack) ->
+      return unless ack?
       unless context.session_store?
         ack error:'Missing session-store.'
       unless data.key?


+ update data while scaling

+ update data while scaling

--- lib/map.coffee
+++ lib/map.coffee
@@ -112,6 +112,7 @@
     @mapLayer.scale(newScale);
     @setDimensions();
     @moveToCenter(center[0], center[1]);
+    @data.updateData();
     @create();

   clearFields: () =>


clarifies tests with methods like `toBeVisible()` etc.

--- spec/javascripts/issue_spec.js.coffee
+++ spec/javascripts/issue_spec.js.coffee
@@ -34,7 +34,7 @@
     # setup
     $btnClose = $('a.btn-close')
     $btnReopen = $('a.btn-reopen')
-    expect($btnReopen.hasClass('hidden')).toBe(true)
+    expect($btnReopen.toBeHidden())
     expect($btnClose.text()).toBe('Close')
     expect(typeof $btnClose.prop('disabled')).toBe('undefined')

@@ -42,11 +42,10 @@
     $btnClose.trigger('click')

     # verify
-    expect($btnClose.hasClass('hidden')).toBe(true)
-    expect($btnReopen.hasClass('hidden')).toBe(false)
-    expect($btnClose.prop('disabled')).toBe(false)
-    expect($('div.issue-box-open').hasClass('hidden')).toBe(true)
-    expect($('div.issue-box-closed').hasClass('hidden')).toBe(false)
+    expect($btnClose.toBeHidden())
+    expect($btnReopen.toBeVisible())
+    expect($('div.issue-box-open').toBeVisible())
+    expect($('div.issue-box-closed').toBeHidden())

     # teardown
   it 'reopens an issue', ->
@@ -58,17 +57,15 @@
     # setup
     $btnClose = $('a.btn-close')
     $btnReopen = $('a.btn-reopen')
-    expect(typeof $btnReopen.prop('disabled')).toBe('undefined')
     expect($btnReopen.text()).toBe('Reopen')

     # excerize
     $btnReopen.trigger('click')

     # verify
-    expect($btnReopen.hasClass('hidden')).toBe(true)
-    expect($btnClose.hasClass('hidden')).toBe(false)
-    expect($btnReopen.prop('disabled')).toBe(false)
-    expect($('div.issue-box-open').hasClass('hidden')).toBe(false)
-    expect($('div.issue-box-closed').hasClass('hidden')).toBe(true)
+    expect($btnReopen.toBeHidden())
+    expect($btnClose.toBeVisible())
+    expect($('div.issue-box-open').toBeVisible())
+    expect($('div.issue-box-closed').toBeHidden())

     # teardown


Remove custom settings after tests

Remove custom settings after tests

--- spec/mixed-indent-warning-spec.coffee
+++ spec/mixed-indent-warning-spec.coffee
@@ -62,6 +62,9 @@

   beforeEach ->
     activationPromise = atom.packages.activatePackage('mixed-indent-warning')
+
+  afterEach ->
+    atom.config.unset('mixed-indent-warning.liveUpdate')

   describe "when live updating is enabled", ->
     beforeEach ->


rename to vulcan-make

rename to vulcan-make

--- server/web.coffee
+++ server/web.coffee
@@ -46,7 +46,7 @@
       res.write "done\n"
       res.write "Building with: #{command}\n"
       log.info  "spawning build"
-      make = spawner.spawn "bin/make \"#{id}\"", env:
+      make = spawner.spawn "bin/vulcan-make \"#{id}\"", env:
         CLOUDANT_URL: process.env.CLOUDANT_URL
         PATH: process.env.PATH
       make.on "error", (err)  -> log.error(err)


Fix specs: jasmine upgrade changed spy API

No assertion was running before because the for-loop would not iterate: calls.length was undefined. Yay coffeescript :(

--- spec/loaders/abstract-loader-shared-behavior.coffee
+++ spec/loaders/abstract-loader-shared-behavior.coffee
@@ -473,9 +473,11 @@
     it 'respects "cache" option in nested includes', ->
       spyOn(loader.storageManager, 'loadObject')
       loader._loadAdditionalIncludes()
-
-      for call in loader.storageManager.loadObject.calls
-        expect(call.args[1].cache).toBeFalsey
+      calls = loader.storageManager.loadObject.calls
+      expect(calls.count()).toBeGreaterThan(0)
+
+      for call in calls.all()
+        expect(call.args[1].cache).toBe(false)

     it 'creates a request for each additional include and calls #_onLoadingCompleted when they all are done', ->
       promises = []


run: catch config errors

run: catch config errors

--- lib/run.coffee
+++ lib/run.coffee
@@ -113,6 +113,7 @@
   for id, node of @config.nodes
     @contexts.push context @contexts, params, node.services, node.config
   # Configuration
+  errors = []
   for service_id in service_ids
     service = @config.services[service_id]
     continue unless service # Optional service
@@ -127,8 +128,12 @@
           throw Error "Invalid configure module: #{JSON.stringify configure}" if typeof configure_fn.call isnt 'function'
           configure_fn.call context
         catch e
-          console.log 'Catch error: ', e
-          return
+          errors.push e
+  if errors.length
+    throw errors[0] if errors.length is 1
+    error = Error "Multiple Errors"
+    error.errors = errors
+    throw error
   @
 util.inherits Run, EventEmitter


Dropbox now support creating and selecting files

--- salvus/page/dropbox.cjsx
+++ salvus/page/dropbox.cjsx
@@ -6,13 +6,39 @@
         getInitialState: ->
           {
             selected: null
+            new_folder: "something"
           }
+
+        click: (folder) ->
+          =>
+            @props.setFolderSelection(folder)
+
+        newFolder: ->
+          @props.newFolder(@state.new_folder)
+
+        onNewFolderChange: (event) ->
+          @setState { new_folder: event.target.value }
+
         render: ->
-          folders = @props.folders.map (folder) ->
{folder}

-
Please select a folder from <tt>{@props.folderPath}</tt>
-

-              {folders}
-

+          folders = @props.folders.map (folder) =>
{folder}

+
+

+

+              <form>
+

+                  Create a new folder in your Dropbox account to sync to your project
+                  <input onChange={@onNewFolderChange} className="form-control" type="text" name="new-folder" value={@state.new_folder} />
+

+                <button type="button" className="btn btn-default" onClick={@newFolder}>Create</button>
+             </form>
+

+

+

+
Or, please select a folder from <tt>{@props.folderPath}</tt>

+

+               {folders}
+

+

       DropboxButton = React.createClass
@@ -37,6 +63,7 @@
             authorized: false
             folders: []
             folderPath: null
+            processing: false
           }
         readFolder: (path) ->
           @state.client.readdir path, null, (error, files, stat, stats) =>
@@ -46,10 +73,24 @@
           @setState { client: client, authorized: true }, ->
             @readFolder('/')
         setFolderSelection: (path) ->
+          # we're done... so send this to the backend
+          console.log("Sending folder", path, "to backend")
+          @setState { processing: true }
+        newFolder: (folder) ->
+          @setState { processing: true }
+          console.log("Creating new folder", folder)
+          path = @state.folderPath + '/' + folder
+          @state.client.mkdir path, (error, stat) =>
+            if error
+              alert(error)
+              return
+            @setFolderSelection(path)
         render: ->
-          if @state.authorized
+          if @state.processing
+
Processing

+          else if @state.authorized
             <DropboxFolderSelector client={@state.client} folders={@state.folders} folderPath={@state.folderPath}
-              setFolderPath={@readFolder} setFolderSelection={@setFolderSelection} />
+              setFolderPath={@readFolder} setFolderSelection={@setFolderSelection} newFolder={@newFolder} />
           else
             <DropboxButton setClient={@setClient} />


show pagination at the bottom of the cards when user agent is mobile

--- lib/carte/client/views/list.cjsx
+++ lib/carte/client/views/list.cjsx
@@ -3,6 +3,8 @@
 React = require('react')
 Cards = require('./cards')
 CardCollection = require('../models/cards')
+Pagination = require('./pagination')
+helpers = require('../helpers')

 module.exports = React.createClass
   displayName: 'List'
@@ -78,48 +80,7 @@

               else
-

-

-                    {
-                      if @props.cards.page
-                        if @props.cards.page.current > 1
-                          href = "/#/?" + @pageParam(@props.cards.page.current - 1)
-                        else
-                          href = "/#/?" + @pageParam(@props.cards.page.total)
-                      else
-                        href = "javascript:void(0)"
-
-                        «
-
-                    }
-

-

-                    {
-                      if @props.cards.page
-
-                          {@props.cards.page.current} / {@props.cards.page.total}
-
-                      else
-
-
-
-                    }
-

-

-                    {
-                      if @props.cards.page
-                        if @props.cards.page.current < @props.cards.page.total
-                          href = "/#/?" + @pageParam(@props.cards.page.current + 1)
-                        else
-                          href = "/#/?" + @pageParam(1)
-                      else
-                        href = "javascript:void(0)"
-
-                        »
-
-                    }
-

-

+                <Pagination cards={@props.cards} />
             }

@@ -133,4 +94,12 @@

       }
       <Cards cards={@props.cards} />
+      {
+        if !@props.card && helpers.isMobile()
+

+

+              <Pagination cards={@props.cards} />
+

+

+      }


kotlin refactor

kotlin refactor

--- lib/cli/generators/kotlin.coffee
+++ lib/cli/generators/kotlin.coffee
@@ -164,7 +164,7 @@
           s1.push "            removeComponent(Component.#{Name}.ordinal)"
           s1.push "    }"
           s1.push ""
-          s1.push "fun Entity.to#{Name}(value:Boolean):Entity {"
+          s1.push "fun Entity.set#{Name}(value:Boolean):Entity {"
           s1.push "    is#{Name} = value"
           s1.push "    return this"
           s1.push "}"


Add Atom package custom settings

Add Atom package custom settings

--- atom/config.cson
+++ atom/config.cson
@@ -9,7 +9,15 @@
       "welcome"
     ]
   editor:
+    fontFamily: "Source Code Pro"
     invisibles: {}
     showIndentGuide: true
     showInvisibles: true
-    fontFamily: "Source Code Pro"
+  "file-icons":
+    forceShow: true
+    onChanges: true
+  linter:
+    lintOnFly: false
+  pigments:
+    markerType: "dot"
+    mergeColorDuplicates: true


Update Atom git-plus settings

Update Atom git-plus settings

--- atom/config.cson
+++ atom/config.cson
@@ -42,6 +42,8 @@
   "git-plus":
     commits:
       verboseCommits: true
+    diffs:
+      wordDiff: false
     general:
       analytics: false
       openInPane: false


Disable Atom linter by default

Disable Atom linter by default

--- atom/config.cson
+++ atom/config.cson
@@ -6,6 +6,7 @@
     audioBeep: false
     disabledPackages: [
       "exception-reporting"
+      "linter"
     ]
     ignoredNames: [
       ".bundle"


softWrap option added to Atom

softWrap option added to Atom

--- atom/config.cson
+++ atom/config.cson
@@ -3,6 +3,7 @@
     'showOnStartup': false
   'editor':
     'fontSize': 15
+    'softwrap': true
     'invisibles': {}
   'core':
     'themes': [


Disable cmd-\ key binding for toggling tree view

It always gets in the way.

--- atom/keymap.cson
+++ atom/keymap.cson
@@ -4,6 +4,9 @@

   # Disable cmd-o because it always gets in the way of typing cmd-i
   'cmd-o': 'unset!'
+
+  # Disable cmd-\ for toggling tree view because I always accidentally hit it
+  'cmd-\\': 'unset!'

 '.platform-darwin atom-workspace':


Update Atom keymap

Update Atom keymap

--- atom/keymap.cson
+++ atom/keymap.cson
@@ -1,15 +1,18 @@
+'body':
+    'ctrl-tab ^ctrl': 'unset!'
+    'ctrl-tab': 'pane:show-next-item'
+    'ctrl-shift-tab ^ctrl': 'unset!'
+    'ctrl-shift-tab': 'pane:show-previous-item'
+
 "atom-workspace":
-    "ctrl-l": "unset!"
-    "ctrl-l": "nvatom:toggle"
     "ctrl-g": "atom-ctags:toggle-project-symbols"

 "atom-workspace atom-text-editor":
-    "alt-\\": "pane:split-right"
-    "alt--": "pane:split-down"
-    "ctrl-l": "unset!"
-    "ctrl-l": "nvatom:toggle"
+    "alt-h": "pane:split-right"
+    "alt-v": "pane:split-down"
     "ctrl-shift-up": "editor:move-line-up"
     "ctrl-shift-down": "editor:move-line-down"
+    "f12": "atom-ctags:go-to-declaration"

 "atom-text-editor:not([mini])":
     "shift-ctrl-K": "markdown-writer:insert-link"


Fixed truncated comment

Fixed truncated comment

--- admin/static/coffee/tables/table.coffee
+++ admin/static/coffee/tables/table.coffee
@@ -37,7 +37,7 @@
                                 num_available_replicas: table("shards").concatMap( (shard) -> shard ).filter(ignore).filter({state: "ready"}).count()
                                 max_replicas: num_servers
                                 num_replicas_per_shard: table("shards").nth(0).filter(ignore).count()
-                                distribution: table('shards').indexesOf( () -> true ).map( (position) -> #TODO:
+                                distribution: table('shards').indexesOf( () -> true ).map( (position) -> #TODO: waiting for #2980 to get dist info
                                     num_keys: r.random(0, 300000).add(400000)
                                     id: position
                                 )


Tweak

Tweak

--- src/lib/configure.coffee
+++ src/lib/configure.coffee
@@ -11,7 +11,7 @@
 BackboneORM.naming_conventions = ALL_CONVENTIONS.default
 BackboneORM.model_cache = new (require '../cache/model_cache')()

-module.exports = configure = (options={}) ->
+module.exports = (options={}) ->
   for key, value of options
     switch key
       when 'model_cache'


refactor prepare_next_redraw()

refactor prepare_next_redraw()

--- views/puzzle.coffee
+++ views/puzzle.coffee
@@ -343,15 +343,7 @@
   prepare_next_redraw: (x,y) ->
     ctx = @redraw_image.getContext('2d')
     ctx.clearRect(0,0,@width,@height)
-    ht = @height
-    if y < 0
-      ht = ht + y
-      y = 0
-    wd = @width
-    if x < 0
-      wd = wd + x
-      x = 0
-    ctx.drawImage(@canvas,x,y,wd,ht,0,0,wd,ht)
+    ctx.drawImage(@canvas,x,y,@width,@height,0,0,@width,@height)
     @redraw_x = x
     @redraw_y =y


sort ascending

sort ascending

--- app/assets/javascripts/datatables.js.coffee
+++ app/assets/javascripts/datatables.js.coffee
@@ -16,7 +16,7 @@
 jQuery ->
 # Set up datatable
   $('.data-table-new-job').DataTable
-    order: [2, 'desc'],
+    order: [2, 'asc'],
     stateSave: true,
     columnDefs: [{
       orderable: false


Added override for php snippet

Added override for php snippet

--- snippets/php.cson
+++ snippets/php.cson
@@ -1,4 +1,7 @@
 '.text.html.php':
+  '<?php …':
+    'prefix': 'php' # Overrides standard php to exclude ?>
+    'body': '\<?php\n$0'
   'Class & Controller Silverstripe':
     'prefix': 'classcontroller'
     'body': """


fixed overlapping animations:   i.e. fixed persistant animations when the toValue changes before the   first animation completes

--- src/art/engine/animation/persistant_animator.coffee
+++ src/art/engine/animation/persistant_animator.coffee
@@ -8,6 +8,7 @@
   inspectedObjectLiteral
   plainObjectsDeepEq
   isPlainObject
+  eq
 } = Foundation
 {EventedObject} = Events

@@ -410,9 +411,11 @@
     @queueEvent "done"
     @_active = false

-  animateAbsoluteTime: (@_element, @_currentValue, @_toValue, @_currentSecond) ->
-
-    @_activate() unless @_active
+  animateAbsoluteTime: (@_element, @_currentValue, toValue, @_currentSecond) ->
+
+    @_activate() unless @_active && eq toValue, @_toValue
+
+    @_toValue = toValue

     animationSeconds = @getAnimationSeconds()


dictify sigs

dictify sigs

--- src/prime.iced
+++ src/prime.iced
@@ -228,7 +228,8 @@
 # Use the sieve for primality testing, which in the case of regular odd
 # primes is [1,2], and for strong primes is more interesting...
 #
-prime_search = (start, range, sieve, progress_hook, iters=20) ->
+prime_search = ({start, range, sieve, progress_hook, iters}) ->
+  iters or= 20
   pf = new PrimeFinder start, sieve
   pf.setmax range
   pvec = (pp while ((pp = pf.next_weak()).compareTo(BigInteger.ZERO) > 0))
@@ -269,7 +270,6 @@

 #-----------------------

-
 #
 # Find a random prime that is nbits long, with certainty
 # 1 - 2^{-iter}
@@ -278,8 +278,11 @@
 # @param {number} iters The number of time to run Miller-Rabin
 # @param {callback} Callback with the {BigInteger} result
 # @param {function} progress_hook A hook to call to update progress
-#
-random_prime = (nbits, iters, cb, progress_hook) ->
+# @param {BigInteger} e The generated prime must p must have gcd(p-1,e) = 1
+#   if specified. If not, this check isn't performed. This is useful for RSA.
+#   It saves a tiny bit of work, but not much if e = 2^16+1 as usual.
+#
+random_prime = ({nbits, iters, progress_hook, e}, cb) ->
   srf = new StrongRandomFountain()
   sieve = [1,2]
   go = true
@@ -288,9 +291,10 @@
     await srf.recharge defer()
     p = new BigInteger nbits, srf
     p = p.setBit(0).setBit(nbits-1)
-    progress_hook? { what : "guess", p }
-    p = prime_search p, nbits/4, sieve, progress_hook, iters
-    go = (p.compareTo(BigInteger.ZERO) is 0)
+    if not e? or p.subtract(BigInteger.ONE).gcd(e).compareTo(BigInteger.ONE) is 0
+      progress_hook? { what : "guess", p }
+      p = prime_search { start : p, range : nbits/4, sieve, progress_hook, iters }
+      go = (p.compareTo(BigInteger.ZERO) is 0)
   progress_hook? { what : "found", p }
   cb p

@@ -308,7 +312,7 @@
   s = obj.p.toString()
   interval = if obj.total? and obj.i? then "(#{obj.i} of #{obj.total})" else ""
   console.log "+ #{obj.what} #{interval} #{s[0...3]}....#{s[(s.length-6)...]}"
-await random_prime 3072, 10, defer(p), progress_hook
+await random_prime { nbits : 1024, iters : 10, progress_hook, e : nbv((1 << 16) + 1) }, defer p
 console.log p.toString()
 process.exit -1


WIP sankey electrical interconnection

WIP sankey electrical interconnection

--- app/assets/javascripts/d3/sankey.coffee
+++ app/assets/javascripts/d3/sankey.coffee
@@ -1,5 +1,67 @@
 D3.sankey =
   charts:
+    sankey_electrical_interconnection:
+      data:
+        nodes: [
+          {id: 'solar',                        column: 0, label: 'solar',                  color: '#ffcc00'},
+          {id: 'wind',                         column: 0, label: 'wind',                   color: '#63A1C9'},
+          {id: 'biomass_waste',                column: 0, label: 'biomass_waste',          color: '#2ca02c'},
+          {id: 'other_renewables',             column: 0, label: 'other_renewables',       color: '#4465c6'},
+          {id: 'hydrogen',                     column: 0, label: 'hydrogen',               color: '#27d3d3'},
+          {id: 'fossil',                       column: 0, label: 'fossil',                 color: '#252525'},
+          {id: 'nuclear',                      column: 0, label: 'uranium',                color: '#ff7f0e'},
+          {id: 'import1',                      column: 0, label: 'import1',                color: '#b71540'},
+          {id: 'import2',                      column: 0, label: 'import2',                color: '#b71540'},
+          {id: 'import3',                      column: 0, label: 'import3',                color: '#b71540'},
+          {id: 'import4',                      column: 0, label: 'import4',                color: '#b71540'},
+          {id: 'import5',                      column: 0, label: 'import5',                color: '#b71540'},
+          {id: 'import6',                      column: 0, label: 'import6',                color: '#b71540'},
+          {id: 'network',                      column: 1, label: 'network',                color: '#006266'},
+          {id: 'households',                   column: 2, label: 'households',             color: '#4169E1'},
+          {id: 'buildings',                    column: 2, label: 'buildings',              color: '#ADD8E6'},
+          {id: 'transport',                    column: 2, label: 'transport',              color: '#8B0000'},
+          {id: 'industry',                     column: 2, label: 'industry',               color: '#A9A9A9'},
+          {id: 'agriculture',                  column: 2, label: 'agriculture',            color: '#FFD700'},
+          {id: 'flexibility',                  column: 2, label: 'flexibility',            color: '#013220'},
+          {id: 'other',                        column: 2, label: 'other',                  color: '#E07033'},
+          {id: 'transport_losses',             column: 2, label: 'transport_losses',       color: '#DCDCDC'},
+          {id: 'export1',                      column: 2, label: 'export1',                color: '#43464B'},
+          {id: 'export2',                      column: 2, label: 'export2',                color: '#43464B'},
+          {id: 'export3',                      column: 2, label: 'export3',                color: '#43464B'},
+          {id: 'export4',                      column: 2, label: 'export4',                color: '#43464B'},
+          {id: 'export5',                      column: 2, label: 'export5',                color: '#43464B'},
+          {id: 'export5',                      column: 2, label: 'export6',                color: '#43464B'}
+        ]
+        links: [
+          {left: 'solar',                      right: 'network',          gquery: 'solar_to_network_in_sankey', color: '#ffcc00'},
+          {left: 'wind',                       right: 'network',          gquery: 'wind_to_network_in_sankey', color: '#63A1C9'},
+          {left: 'biomass_waste',              right: 'network',          gquery: 'biomass_waste_to_network_in_sankey', color: '#2ca02c'},
+          {left: 'other_renewables',           right: 'network',          gquery: 'other_renewables_to_network_in_sankey', color: '#4465c6'},
+          {left: 'hydrogen',                   right: 'network',          gquery: 'hydrogen_to_network_in_sankey', color: '#27d3d3'},
+          {left: 'fossil',                     right: 'network',          gquery: 'fossil_to_network_in_sankey', color: '#252525'},
+          {left: 'nuclear',                    right: 'network',          gquery: 'nuclear_to_network_in_sankey', color: '#ff7f0e'},
+          {left: 'import1',                    right: 'network',          gquery: 'import1_to_network_in_sankey', color: '#ff7f0e'},
+          {left: 'import2',                    right: 'network',          gquery: 'import2_to_network_in_sankey', color: '#ff7f0e'},
+          {left: 'import3',                    right: 'network',          gquery: 'import3_to_network_in_sankey', color: '#ff7f0e'},
+          {left: 'import4',                    right: 'network',          gquery: 'import4_to_network_in_sankey', color: '#ff7f0e'},
+          {left: 'import5',                    right: 'network',          gquery: 'import5_to_network_in_sankey', color: '#ff7f0e'},
+          {left: 'import6',                    right: 'network',          gquery: 'import6_to_network_in_sankey', color: '#ff7f0e'},
+
+          {left: 'network',                    right: 'households',       gquery: 'network_to_households_in_sankey', color: '#1f77b4'},
+          {left: 'network',                    right: 'buildings',        gquery: 'network_to_buildings_in_sankey', color: '#1f77b4'},
+          {left: 'network',                    right: 'transport',        gquery: 'network_to_transport_in_sankey', color: '#1f77b4'},
+          {left: 'network',                    right: 'industry',         gquery: 'network_to_industry_in_sankey', color: '#1f77b4'},
+          {left: 'network',                    right: 'agriculture',      gquery: 'network_to_agriculture_in_sankey', color: '#1f77b4'},
+          {left: 'network',                    right: 'other',            gquery: 'network_to_other_in_sankey', color: '#1f77b4'},
+          {left: 'network',                    right: 'transport_losses', gquery: 'network_to_transport_losses_in_sankey', color: '#1f77b4'},
+          {left: 'network',                    right: 'flexibility',      gquery: 'network_to_flexibility_in_sankey', color: '#1f77b4'},
+          {left: 'network',                    right: 'export1',          gquery: 'network_to_export1_in_sankey', color: '#1f77b4'},
+          {left: 'network',                    right: 'export2',          gquery: 'network_to_export2_in_sankey', color: '#1f77b4'},
+          {left: 'network',                    right: 'export3',          gquery: 'network_to_export3_in_sankey', color: '#1f77b4'},
+          {left: 'network',                    right: 'export4',          gquery: 'network_to_export4_in_sankey', color: '#1f77b4'},
+          {left: 'network',                    right: 'export5',          gquery: 'network_to_export5_in_sankey', color: '#1f77b4'},
+          {left: 'network',                    right: 'export6',          gquery: 'network_to_export6_in_sankey', color: '#1f77b4'},
+        ]
     biomass_sankey:
       data:
         nodes: [


Remove laggy animation from Sankey chart

--- app/assets/javascripts/d3/sankey.coffee
+++ app/assets/javascripts/d3/sankey.coffee
@@ -524,18 +524,15 @@

       # move the rectangles
       @nodes.selectAll("rect")
-        .transition().duration(500)
         .attr("height", (d) => @y d.value())
         .attr("y", (d) => @y(d.y_offset()))

       # then move the node label
       @nodes.selectAll("text.label")
-        .transition().duration(500)
         .attr("y", (d) => @y(d.y_offset() + d.value() / 2) )

       # then transform the links
       @links.data(@link_list, (d) -> d.cid)
-        .transition().duration(500)
         .selectAll("path")
         .attr("d", (link) => @link_line link.path_points())
         .style("stroke-width", (link) => @y(link.value()))


Sometimes there's still no text

Sometimes there's still no text

--- app/assets/javascripts/notifications.coffee
+++ app/assets/javascripts/notifications.coffee
@@ -33,7 +33,7 @@

     for msg in channel.msgs when parseFloat(msg.ts) > last_read
       text = msg.text || msg.attachments[0].text
-      if text.indexOf(name) != -1
+      if text?.indexOf(name) != -1
         console.log "found unread mention in #{channel.name}"
         count += 1
     if count > 0


Added a user agent header to avoid 403.

Added a user agent header to avoid 403.

--- src/scripts/drupalorg.coffee
+++ src/scripts/drupalorg.coffee
@@ -62,7 +62,7 @@
     url = msg.match[0]
     return if recentLinks.contains url
     recentLinks.add url
-    msg.http(url).get() (err, res, body) ->
+    msg.http(url).headers('User-Agent': 'hubot').get() (err, res, body) ->
       if err
         console.log "Errors getting url: #{url}"
         return


Cleanup document.ready

Cleanup document.ready

--- app/assets/javascripts/graphs.js.coffee
+++ app/assets/javascripts/graphs.js.coffee
@@ -1,4 +1,4 @@
-saveGraph = () ->
+saveGraph = ->
   graph = {}
   graph.entities = getCurrentEntities()
   graph.relationships = getCurrentRelationships()
@@ -14,7 +14,7 @@
   else
     $.ajax({ url:"/graphs", type:"POST", dataType:"json", data:encodeData })

-getCurrentEntities = () ->
+getCurrentEntities = ->
   entities = []
   $("div.ui-dialog").each (index, element) ->
     entities[index] = {}
@@ -27,28 +27,22 @@
     entities[index].attrib = $(element).find("textarea.attributes").val()
   entities

-getCurrentRelationships = () ->
+getCurrentRelationships = ->
   relationships = []
   # Add parameterization code here once relationships are implemented
   relationships

-getSettings = () ->
+getSettings = ->
   settings = {}
   $("#settings").find(".field").find("input,textarea").each (index, element) ->
     setting_name = element.name.replace("graph[","").replace("]","")
     settings[setting_name] = element.value
   settings

-graphID = () ->
+graphID = ->
   window.location.pathname.split('/')[2]

-addRelationshipsToDOM = () ->
-  for relationship, index in window.relationships.length
-    paths = paths + '<path id="relationship' + index + '" stroke="black" stroke-width="2" fill="none" />'
-
-  $('#relationships').html(paths)
-
-$(document).ready ->
+makeEntitiesDraggable = ->
   $(".entity").each (index, element) ->
     entity_width  = parseInt( $(element).attr("data-width"))
     entity_height = parseInt( $(element).attr("data-height"))
@@ -56,22 +50,22 @@
     entity_y      = parseInt( $(element).attr("data-y"))
     $(element).dialog({ width: entity_width, height: entity_height, position: [entity_x,entity_y] })

-  $("#settings").hide()
+addRelationshipsToDOM = ->
+  for relationship, index in window.relationships.length
+    paths = paths + '<path id="relationship' + index + '" stroke="black" stroke-width="2" fill="none" />'
+  $('#relationships').html(paths)

-  $("#save_button").click -> saveGraph()
-
-  alerts = 0
-
-  addRelationshipsToDOM
-
+setupEntityDragHandler = ->
   $('.entity').on 'dialogdrag', (event, ui) ->
-
-    alerts += 1
-    console.log( "Moving entity " + event.currentTarget.dataset.id ) if alerts % 30 == 0
-
     x1 = ui.position.left
     y1 = ui.position.top - $('#header_bar').height()
     x2 = 0 # window.relationships[] # which entity is being dragged??
     y2 = 0 # window.relationships[]
     $('#relationship1').attr('d', "M #{x1} #{y1} L #{x2} #{y2}")

+$(document).ready ->
+  makeEntitiesDraggable()
+  addRelationshipsToDOM()
+  setupEntityDragHandler()
+  $("#settings").hide()
+  $("#save_button").click -> saveGraph()


Fix A/B case

Fix A/B case

--- src/desktop/components/split_test/middleware.coffee
+++ src/desktop/components/split_test/middleware.coffee
@@ -17,7 +17,7 @@
       res.locals.sd[k.toUpperCase()] = v

   # TODO: Remove when the client nav AB test ends.
-  if runningTests['CLIENT_NAVIGATION_V3']
+  if runningTests['client_navigation_v3']
     res.locals.sd['EXPERIMENTAL_APP_SHELL'] = Boolean(res.locals.sd['CLIENT_NAVIGATION_V3'] is 'experiment')

   # Store value in globally available location.


fixing routes

fixing routes

--- app/assets/javascripts/router.js.coffee
+++ app/assets/javascripts/router.js.coffee
@@ -1,7 +1,7 @@
 # For more information see: http://emberjs.com/guides/routing/

 Theoj.Router.map ()->
-
+
   @route    'papers'      , {path:'/papers/:type'}
   @route    'submit_paper', {path:'/submit'}
   @resource 'paper'       , {path:'/paper/:id'}
@@ -13,5 +13,4 @@

   @route 'about', path :'/about'
   @route 'people', path :'/people'
-  @resource('papers')
   @route 'about', path: '/about'


getActiveEditor -> getActiveTextEditor

getActiveEditor -> getActiveTextEditor

--- lib/sumatra-forwardsearch.coffee
+++ lib/sumatra-forwardsearch.coffee
@@ -28,7 +28,7 @@
   forwardsearch: ->
     path = require("path")
     command = path.join(__dirname, 'calldde.exe');
-    editor = atom.workspace.getActiveEditor();
+    editor = atom.workspace.getActiveTextEditor();
     filename = editor.getPath()
     args = [filename, (editor.cursors[0].getBufferRow() + 1).toString()]
     stdout = (output) ->


ensure n > 0 for unstable data highlight on radar

--- app/assets/javascripts/zvg/radar.coffee
+++ app/assets/javascripts/zvg/radar.coffee
@@ -138,7 +138,8 @@
       .attr('d', (d) => @polygon(d.points))
       .style('stroke', (d) =>
         t = @_n_threshold
-        mock_n = if d.n_values.some((n) -> n < t)
+        # checking n > 0 ensures that missing data doesn't spoil the rest of the plot
+        mock_n = if d.n_values.some((n) -> n > 0 and n < t)
           0
         else
           d.n_values[0]


commented out rpc debug message

commented out rpc debug message

--- app/js/services/rpc.coffee
+++ app/js/services/rpc.coffee
@@ -15,25 +15,7 @@
         angular.extend(http_params.data, reqparams)
         #console.log "RpcService <#{http_params.data.method}>, stack: #{getStackTrace()}"
         $http(http_params).then (response) ->
-            console.log("RpcService <#{http_params.data.method}> response:", response, "params:", params) if not (method in
-                #filter out re-occuring rpc calls
-                ["wallet_open","wallet_lock","wallet_unlock","wallet_create","batch",
-                "wallet_get_info","wallet_get_setting","wallet_account_balance",
-                "wallet_set_setting","wallet_account_transaction_history ",
-                "wallet_market_order_list","wallet_get_account","wallet_account_yield",
-                "wallet_get_transaction_fee","wallet_list_accounts",
-                "wallet_account_vote_summary",
-                "get_info","get_config",
-                "blockchain_list_assets","blockchain_get_asset",
-                "blockchain_list_delegates","blockchain_get_account",
-                "blockchain_get_security_state","blockchain_get_info",
-                "blockchain_market_list_asks","wallet_account_transaction_history",
-                "blockchain_market_list_covers","blockchain_market_list_bids",
-                "blockchain_market_status","blockchain_market_order_history",
-                "blockchain_get_feeds_for_asset","blockchain_market_get_asset_collateral",
-                "blockchain_market_list_shorts","blockchain_market_price_history"
-                ]
-            )
+            #console.log("RpcService <#{http_params.data.method}> response:", response, "params:", params) #unless @polling(method)
             response.data or response

     start_profiler: ->
@@ -49,3 +31,23 @@
         console.log "------ profiler output ------>"
         console.log(a[0],a[1],a[2],a[3]) for a in results
         window.rpc_calls_performance_data = null
+
+    # temp enable for debugging
+#    polling: (method) ->
+#        method in [
+#            "wallet_open","wallet_lock","wallet_unlock","wallet_create","batch",
+#            "wallet_get_info","wallet_get_setting","wallet_account_balance",
+#            "wallet_set_setting","wallet_account_transaction_history ",
+#            "wallet_market_order_list","wallet_get_account","wallet_account_yield",
+#            "wallet_get_transaction_fee","wallet_list_accounts",
+#            "wallet_account_vote_summary",
+#            "get_info","get_config",
+#            "blockchain_list_assets","blockchain_get_asset",
+#            "blockchain_list_delegates","blockchain_get_account",
+#            "blockchain_get_security_state","blockchain_get_info",
+#            "blockchain_market_list_asks","wallet_account_transaction_history",
+#            "blockchain_market_list_covers","blockchain_market_list_bids",
+#            "blockchain_market_status","blockchain_market_order_history",
+#            "blockchain_get_feeds_for_asset","blockchain_market_get_asset_collateral",
+#            "blockchain_market_list_shorts","blockchain_market_price_history"
+#        ]


testing ssh

testing ssh

--- app/coffeescripts/LayersMenuView.coffee
+++ app/coffeescripts/LayersMenuView.coffee
@@ -26,7 +26,7 @@
     $("##{@options.parentId}").append(@el)
     @

-  #these open and close the Layers Menu itself
+  #these open and close the Layers Menu itself
   displayOff: (e) =>
     @$el.removeClass "open"


Fix session loading after all other dependencies and hanging the level load

--- app/lib/LevelLoader.coffee
+++ app/lib/LevelLoader.coffee
@@ -209,8 +209,12 @@
         console.log "Pushing resource: ", heroResource if LOG
         @worldNecessities.push heroResource
       @sessionDependenciesRegistered[session.id] = true
+    unless @level.isType('hero', 'hero-ladder', 'hero-coop')
+      # Return before loading heroConfig ThangTypes. Finish if all world necessities were completed by the time the session loaded.
+      if @checkAllWorldNecessitiesRegisteredAndLoaded()
+        @onWorldNecessitiesLoaded()
       return
-    return unless @level.isType('hero', 'hero-ladder', 'hero-coop')
+    # Load the ThangTypes needed for the session's heroConfig for these types of levels
     heroConfig = session.get('heroConfig')
     heroConfig ?= me.get('heroConfig') if session is @session and not @headless
     heroConfig ?= {}


Improve the vierw helper to be able to deal with meta data.

--- app/lib/view_helper.coffee
+++ app/lib/view_helper.coffee
@@ -1,4 +1,7 @@
 # Put your handlebars.js helpers here.
+# Globals variables.
+# Get the domains definition as globals.
+domains_definition = require('./domains')

 Handlebars.registerHelper 'pick', (val, options) ->
 	return options.hash[val]
@@ -37,6 +40,21 @@
     console.log "Value"
     console.log "===================="
     console.log optionalValue
+# Get the metadatad for a property.
+getMetadataFor = (property, context)->
+  metadata = {}
+  if context? and context['metadatas']?
+    md = context['metadatas'][property]
+    if md?
+      # First copy the "globals" metadata
+      _.extend(metadata, md.metadata) if md.metadata?
+      # Extend the "overriden" metadatas
+      _.extend(metadata, {required: md.required}) if md.required?
+      _.extend(metadata, {label: md.label}) if md.label?
+      _.extend(metadata, {domain: md.domain}) if md.domain?
+      console.log "property", property,"metadata", metadata
+  return metadata
+

 ###------------------------------------------- FORM FOR THE INPUTS -------------------------------------------###
 Handlebars.registerHelper "input_for", (property, options) ->
@@ -46,9 +64,19 @@
   dataType = undefined
   #Read all the options if they exists
   opt = options.hash or {}
-  isRequired = if !opt.isRequired? or !opt.isRequired then "" else "*"
+  metadata = getMetadataFor(property, this)
+  domain = domains_definition[metadata.domain] or {}
+  console.log "domain", domain
+  isRequired = ()=>
+    isDisplayRequired = false
+    if opt.isRequired?
+      isDisplayRequired = opt.isRequired
+    else if metadata.required?
+      isDisplayRequired = metadata.required
+    return if isDisplayRequired then "*" else ""
+  #isRequired = if !opt.isRequired? or !opt.isRequired then "" else "*"
   translationRoot = opt.translationRoot or undefined
-  dataType = opt.dataType or "text"
+  dataType = opt.dataType or domain.type or "text"
   readonly = opt.readonly or false
   readonly = if readonly then "readonly" else ""
   disabled = opt.disabled or false
@@ -80,16 +108,26 @@
       else return "value='#{_.escape(this[property])}'"
     ""
   #Get the value of the transalated label.
-  translationKey = i18n.t(((if (translationRoot?) and typeof translationRoot is "string" then translationRoot + "." else "")) + property)
+  translationKey =()=>
+    translation = metadata.label or ("#{this['modelName']}.#{property}" if this['modelName']?) or ""
+    if translationRoot?
+      translation = ((if (translationRoot?) and typeof translationRoot is "string" then translationRoot + "." else "")) + property
+
+    return if(translation is "") then "" else i18n.t(translation)
+
   #Deal with the icon case
   icon = ()=>
     #
     if opt.icon? then " " else ""
   #Deal with the label
   label = ()=>
-    if not opt.isNoLabel? then " #{translationKey} " else ""
+    if opt.isNoLabel?
+      return ""
+    else
+      return "#{translationKey()}"
+    #if not opt.isNoLabel? then " #{translationKey} " else ""
   #By default there is a placeholder or if the preperty is true.
-  placeholder = if !opt.placeholder? or opt.placeholder then "placeholder='#{translationKey}'" else ""
+  placeholder = if !opt.placeholder? or opt.placeholder then "placeholder='#{translationKey()}'" else ""
   #Initialize the errors variables => Is there an error, if yes what is the message.
   error = ""
   error = "has-error" if @errors? and @errors[property]?
@@ -112,7 +150,7 @@

                #{icon()}
               <input id='#{property}' class='form-control input-sm' data-name='#{property}' type='#{dataType}' #{inputAttributes} #{placeholder} #{propertyValue()} #{readonly} #{disabled}/>
-              #{isRequired}
+              #{isRequired()}

             #{errors()}


Use ? to test for existence

Use ? to test for existence

--- src/components/reference-book/exercise.cjsx
+++ src/components/reference-book/exercise.cjsx
@@ -14,9 +14,8 @@
   displayName: 'ReferenceBookExercise'
   render: ->
     {exerciseAPIUrl} = @props
-    exercise = ReferenceBookExerciseStore.get(exerciseAPIUrl)
-    return null unless exercise
-    {items} = exercise
+    {items} = ReferenceBookExerciseStore.get(exerciseAPIUrl)?
+
     unless items?.length
       # warning about missing exercise --
       # is there a need to show the reader anything?


Check last user to ask.

Check last user to ask.

--- src/dynamodb-brain.coffee
+++ src/dynamodb-brain.coffee
@@ -69,6 +69,8 @@
     saveBrain data

   robot.respond /brainscan/, (res) ->
+    before = robot.brain.get "LastUser"
+    res.reply "#{before} just asked me this!"
     robot.brain.set "LastUser", res.message.user.name
     res.reply "My brain is called '#{params.Key.botname}' and it's doing great!"


metric: added d7

metric: added d7

--- src/models/metric.coffee
+++ src/models/metric.coffee
@@ -73,6 +73,23 @@
       where: (day) ->
         "time >= #{dayToMS day - 1}ms AND time < #{dayToMS day}ms AND " +
         "joinDay = '#{day - 1}'"
+  }
+  {
+    name: 'D7 Retention'
+    format: '0.00%'
+    isPercent: true
+    numerator:
+      select: 'count(distinct(userId))'
+      from: 'view'
+      where: (day) ->
+        "time >= #{dayToMS day}ms AND time < #{dayToMS day + 1}ms AND " +
+        "joinDay = '#{day - 7}'"
+    denominator:
+      select: 'count(distinct(userId))'
+      from: 'view'
+      where: (day) ->
+        "time >= #{dayToMS day - 7}ms AND time < #{dayToMS day - 6}ms AND " +
+        "joinDay = '#{day - 7}'"
   }
   {
     name: 'sessions / DAU'


fix lastfm regex

fix lastfm regex

--- scripts/lastfm_np.coffee
+++ scripts/lastfm_np.coffee
@@ -17,13 +17,14 @@
 #   sn0opy

 getSong = (msg, usr) ->
-  user = usr ? msg.match[2]
+  user = usr ? msg.match[1]
   apiKey = process.env.HUBOT_LASTFM_APIKEY
   msg.http('http://ws.audioscrobbler.com/2.0/?')
     .query(method: 'user.getrecenttracks', user: user, api_key: apiKey, format: 'json')
     .get() (err, res, body) ->
       results = JSON.parse(body)
       if results.error
+        msg.send user
         msg.send results.message
         return
       song = results.recenttracks.track[0]


change the error message once again

change the error message once again

--- src/smc-webapp/editor.coffee
+++ src/smc-webapp/editor.coffee
@@ -1526,7 +1526,7 @@
             # WARNING: As far as I can tell, this doesn't call FileEditor.save
             if err
                 if redux.getProjectStore(@project_id).is_file_open(@filename)  # only show error if file actually opened
-                    alert_message(type:"error", message:"Error saving '#{@filename}' (#{err})")
+                    alert_message(type:"error", message:"Error saving '#{@filename}' (#{err}) -- (you might need to close and open this file or restart this project)")
             else
                 @emit('saved')
             @save_button.icon_spin(false)


fullpieagent testing

fullpieagent testing

--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -136,199 +136,3 @@
             '(' + Math.round(data[i].value/sum * 100) + '%)'
         )

-    update: (data) ->
-        #console.log("update pie", data);
-
-        that = this;
-
-        this.piedata = this.pie(data);
-
-        //create a marker element if it doesn't already exist
-        defs = this.svg.select("defs");
-        if (defs.empty() ) {
-            defs = this.svg.append("defs");
-        }
-        marker = defs.select("marker#circ");
-        if (marker.empty() ) {
-            defs.append("marker")
-            .attr("id", "circ")
-            .attr("markerWidth", 6)
-            .attr("markerHeight", 6)
-            .attr("refX", 3)
-            .attr("refY", 3)
-            .append("circle")
-            .attr("cx", 3)
-            .attr("cy", 3)
-            .attr("r", 3);
-        }
-        #Create/select <g> elements to hold the different types of graphics
-        #and keep them in the correct drawing order
-        pathGroup = this.svg.select("g.piePaths");
-        if (pathGroup.empty() ){
-            pathGroup = this.svg.append("g")
-                       .attr("class", "piePaths");
-        }
-        pointerGroup = this.svg.select("g.pointers")
-        if (pointerGroup.empty() ) {
-            pointerGroup = this.svg.append("g")
-                           .attr("class", "pointers");
-        }
-        labelGroup = this.svg.select("g.labels")
-        if (labelGroup.empty() ) {
-            labelGroup = this.svg.append("g")
-                         .attr("class", "labels");
-        }
-
-        this.path = pathGroup.selectAll("path.pie")
-            .data(this.piedata);
-
-        this.path.enter().append("path")
-            .attr("class", "pie")
-            .attr("fill", (d, i) ->
-                return that.color(i);
-        );
-
-#        this.path.transition()
-#            .duration(300)
-#            .attrTween("d", that.pieTween);
-
-#        this.path.exit()
-#            .transition()
-#            .duration(300)
-#            .attrTween("d", that.removePieTween)
-#            .remove();
-
-        labels = labelGroup.selectAll("text")
-            .data(this.piedata
-                .sort((p1,p2) ->
-                    return p1.startAngle - p2.startAngle;
-                )
-            );
-        labels.enter()
-            .append("text")
-            .attr("text-anchor", "middle");
-        labels.exit()
-            .remove();
-
-
-        labelLayout = d3.geom.quadtree()
-            .extent([[-that.width,-that.height], [that.width, that.height] ])
-            .x((d) -> return d.x;)
-            .y((d) -> return d.y;)
-            ([]); #create an empty quadtree to hold label positions
-        maxLabelWidth = 0;
-        maxLabelHeight = 0;
-
-        labels.text((d) ->
-            # Set the text *first*, so we can query the size
-            # of the label with .getBBox()
-            return d.value;
-        )
-        .each((d, i) ->
-            # Move all calculations into the each function.
-            # Position values are stored in the data object
-            # so can be accessed later when drawing the line
-
-            # calculate the position of the center marker
-            a = (d.startAngle + d.endAngle) / 2 ;
-
-            #trig functions adjusted to use the angle relative
-            #to the "12 o'clock" vector:
-            d.cx = Math.sin(a) * (that.radius - 75);
-            d.cy = -Math.cos(a) * (that.radius - 75);
-
-            # calculate the default position for the label,
-            #   so that the middle of the label is centered in the arc
-            bbox = this.getBBox();
-            #bbox.width and bbox.height will
-            #describe the size of the label text
-            labelRadius = that.radius - 20;
-            d.x =  Math.sin(a) * (labelRadius);
-            d.l = d.x - bbox.width / 2 - 2;
-            d.r = d.x + bbox.width / 2 + 2;
-            d.y = -Math.cos(a) * (that.radius - 20);
-            d.b = d.oy = d.y + 5;
-            d.t = d.y - bbox.height - 5 ;
-
-            # check whether the default position
-            #   overlaps any other labels
-            conflicts = [];
-            labelLayout.visit((node, x1, y1, x2, y2) ->
-                #recurse down the tree, adding any overlapping
-                #node is the node in the quadtree,
-                #node.point is the value that we added to the tree
-                #x1,y1,x2,y2 are the bounds of the rectangle that
-                #this node covers
-
-                if (  (x1 > d.r + maxLabelWidth/2)
-                        #left edge of node is to the right of right edge of label
-                    ||(x2 < d.l - maxLabelWidth/2)
-                        #right edge of node is to the left of left edge of label
-                    ||(y1 > d.b + maxLabelHeight/2)
-                        #top (minY) edge of node is greater than the bottom of label
-                    ||(y2 < d.t - maxLabelHeight/2 ) )
-                        #bottom (maxY) edge of node is less than the top of label
-
-                      return true; #don't bother visiting children or checking this node
-
-                p = node.point;
-                v = false, h = false;
-                if ( p ) { #p is defined, i.e., there is a value stored in this node
-                    h =  ( ((p.l > d.l) && (p.l <= d.r))
-                       || ((p.r > d.l) && (p.r <= d.r))
-                       || ((p.l < d.l)&&(p.r >=d.r) ) ); //horizontal conflict
-
-                    v =  ( ((p.t > d.t) && (p.t <= d.b))
-                       || ((p.b > d.t) && (p.b <= d.b))
-                       || ((p.t < d.t)&&(p.b >=d.b) ) ); //vertical conflict
-
-                    if (h&&v)
-                        conflicts.push(p); //add to conflict list
-                }
-
-            );
-
-            if (conflicts.length) {
-                console.log(d, " conflicts with ", conflicts);
-                rightEdge = d3.max(conflicts, (d2) ->
-                    return d2.r;
-                );
-
-                d.l = rightEdge;
-                d.x = d.l + bbox.width / 2 + 5;
-                d.r = d.l + bbox.width + 10;
-            }
-            else console.log("no conflicts for ", d);
-
-            # add this label to the quadtree, so it will show up as a conflict
-            #   for future labels.
-            labelLayout.add( d );
-            maxLabelWidth = Math.max(maxLabelWidth, bbox.width+10);
-            maxLabelHeight = Math.max(maxLabelHeight, bbox.height+10);
-        )
-        #.transition()//we can use transitions now!
-        .attr("x", (d) ->
-                    return d.x;
-                )
-                .attr("y", (d) ->
-                    return d.y;
-                );
-
-
-        pointers = pointerGroup.selectAll("path.pointer")
-            .data(this.piedata);
-        pointers.enter()
-            .append("path")
-            .attr("class", "pointer")
-            .style("fill", "none")
-            .style("stroke", "black")
-            .attr("marker-end", "url(#circ)");
-        pointers.exit().remove();
-
-        pointers.transition().attr("d", (d) ->
-            if (d.cx > d.l) {
-                return "M" + (d.l+2) + "," + d.b + "L" + (d.r-2) + "," + d.b + " " + d.cx + "," + d.cy;
-            } else {
-                return "M" + (d.r-2) + "," + d.b + "L" + (d.l+2) + "," + d.b + " " + d.cx + "," + d.cy;
-            }
-        );


sagews: make worksheet more defense against possible output corruption

--- src/smc-webapp/sagews.coffee
+++ src/smc-webapp/sagews.coffee
@@ -2304,7 +2304,12 @@
         return @cm.getLine(x.loc.from.line)

     output: =>
-        return (misc.from_json(x) for x in @raw_output().slice(38).split(MARKERS.output) when x)
+        for x in @raw_output().slice(38).split(MARKERS.output)
+            try
+                v.push(misc.from_json(x))
+            catch
+                console.warn("unable to read json message in worksheet: #{x}")
+        return v

     _get_output: () =>
         n = @end_line()


Agent pie: start/end angle pie transition work

--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -155,7 +155,7 @@
         path = pathGroup.selectAll('path.pie').data(piedata)
         path.enter().append('path').attr('class', 'pie').attr('fill', (d, i) -> color i)

-        path.transition().duration(750).attrTween('d', (d,i) ->
+        path.transition().duration(750).attrTween('d', (d,i, @oldPieData) ->
             theOldDataInPie = @oldPieData ? piedata
             console.log('@oldPieData in the tween',@oldPieData)
             # Interpolate the arcs in data space


linebreaks in the rewriter ... cosmetic.

--- src/rewriter.coffee
+++ src/rewriter.coffee
@@ -67,7 +67,8 @@
         else
           @tokens.splice i, 0, ['TERMINATOR', "\n", prev[2]]
         return 2
-      else if before and before[0] is 'OUTDENT' and prev and prev[0] is 'TERMINATOR' and post and post[0] is 'TERMINATOR' and after and after[0] is 'ELSE'
+      else if before and before[0] is 'OUTDENT' and prev and prev[0] is 'TERMINATOR' and
+              post and post[0] is 'TERMINATOR' and after and after[0] is 'ELSE'
           @tokens.splice i + 1, 0, @tokens.splice(i - 2, 1)[0]
       return 1


fullpieagent testing

fullpieagent testing

--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -47,7 +47,7 @@
       endAngle: d.endAngle)
     (t) ->
       b = i(t)
-      return arc b
+      return @arc b
   removePieTween: (d, i) ->
     `var i`
     that = this
@@ -255,7 +255,7 @@
     radiusi = 90
     color = d3.scale.category20()
     pie = d3.layout.pie().value((d) -> d.value)
-    arc = d3.svg.arc().innerRadius(radiusi).outerRadius(radiuso)
+    @arc = d3.svg.arc().innerRadius(radiusi).outerRadius(radiuso)
     svg = d3.select(@node).append('svg').attr('width', width).attr('height', height).append('g').attr('transform', 'translate(' + width / 2 + ',' + height / 2 + ')')
     console.log 'update pie', dataSet
     that = this


Better regex

Better regex

--- app/controllers/subject-selector.coffee
+++ app/controllers/subject-selector.coffee
@@ -8,7 +8,7 @@
     @onHashChange()

   onHashChange: ->
-    cleanHash = location.hash.match(/([\w]+[\w\/]+)/)
+    cleanHash = location.hash.match(/([\w]+[\w\/]+)/gi)
     pieces = cleanHash?.pop().split('/')
     return unless pieces?[0] is 'subjects'


--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -179,7 +179,7 @@
             #    b = i(t)
             #    return arc b
         #)
-        path.transition().duration(750).attrTween('a', (d) ->
+        path.transition().duration(750).attrTween('a', (d,i,a) ->
             `var i`
             i = d3.interpolate(@_current, a)
             @_current = i(0)


--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -398,4 +398,5 @@
         #window.oldPieData[@instanceDataId] = piedata
         window.oldPieData = window.oldPieData or {}
         window.oldPieData[@instanceDataId] = window.oldPieData[@instanceDataId] or {}
+        console.log('piedata',piedata)
         return