SocialWorker: remove monitoring methods

SocialWorker: remove monitoring methods

--- workers/datadog.coffee
+++ workers/datadog.coffee
@@ -2,8 +2,7 @@
 { argv }        = require 'optimist'
 KONFIG          = require('koding-config-manager').load("main.#{argv.c}")
 monitor         = require('appmetrics').monitor()
-{ DogStatsD
-  MetricsBase } = require 'koding-datadog'
+{ MetricsBase } = require 'koding-datadog'

 module.exports = class Metrics extends MetricsBase

@@ -47,77 +46,3 @@
     @sendMetrics @getMethodMetrics(opts), metricName, tags

-  @populateNodejsTags : (tags) ->
-
-    tags ?= []
-
-    tagList = {
-      os      : [ 'type', 'release', 'hostname' ]
-      process : [ 'cwd', 'pid', 'argv', 'title', 'getuid', 'getgid',
-        'version', 'platform', 'execPath' ]
-    }
-
-    for key, value of tagList
-      for prop in value
-        tagName  = "#{key}.#{prop}"
-        tagValue = ''
-
-        source = switch
-          when key is 'process'  then process
-          when key is 'os'       then os
-
-        tagValue = switch
-          when not source[prop]                   then 0
-          when Array.isArray source[prop]         then source[prop].join('-')
-          when typeof source[prop] is 'function'  then source[prop].call()
-          else                                    source[prop]
-
-        tagValue = @sanitizeTagValue tagValue
-        tags.push "#{tagName}:#{tagValue}"
-
-    return tags
-
-
-  @startMonitoringNodejs : ->
-
-    tags = @populateCommonTags()
-    tags = @populateNodejsTags(tags)
-
-    monitor.on 'eventloop', (eventloop) ->
-      eventloopMetrics =
-        gauge           :
-          'latency.min' : eventloop.latency.min
-          'latency.max' : eventloop.latency.max
-          'latency.avg' : eventloop.latency.avg
-
-      Metrics.sendMetrics eventloopMetrics, 'nodejs.eventloop', tags
-
-    monitor.on 'memory', (memory) ->
-      memMetrics =
-        gauge                :
-          'process.private'  : memory.private
-          'process.physical' : memory.physical
-          'process.virtual'  : memory.virtual
-          'system.used'      : memory.physical_used
-          'system.total'     : memory.physical_total
-
-      Metrics.sendMetrics memMetrics, 'nodejs.memory', tags
-
-    monitor.on 'gc', (gc) ->
-      gcMetrics =
-        gauge      :
-          size     : gc.size
-          used     : gc.used
-          duration : gc.duration
-
-      Metrics.sendMetrics gcMetrics, 'nodejs.gc', tags
-
-    monitor.on 'cpu', (cpu) ->
-      cpuMetrics =
-        gauge     :
-          process : cpu.process
-          system  : cpu.system
-
-      Metrics.sendMetrics cpuMetrics, 'nodejs.cpu', tags
-
-


Typo in tools view: hasPermissions -> hasPermission

--- assets/scripts/app/views/repo/show.coffee
+++ assets/scripts/app/views/repo/show.coffee
@@ -129,7 +129,7 @@

     canRequeueBuild: (->
       @get('displayRequeueBuild') && @get('hasPermission')
-    ).property('displayRequireBuild', 'hasPermissions')
+    ).property('displayRequireBuild', 'hasPermission')

     displayRequeueJob: (->
       @get('isJobTab') && @get('job.isFinished')
@@ -137,7 +137,7 @@

     canRequeueJob: (->
       @get('displayRequeueJob') && @get('hasPermission')
-    ).property('displayRequeueJob', 'hasPermissions')
+    ).property('displayRequeueJob', 'hasPermission')

     showDownloadLog: (->
       @get('jobIdForLog')


Change message.cson to create instance section in flow

--- src/jobs/instances-get/message.cson
+++ src/jobs/instances-get/message.cson
@@ -1,7 +1,7 @@
 {
   type: 'object'
-  title: 'Get Project'
-  'x-group-name': 'Projects'
+  title: 'Get Instance'
+  'x-group-name': 'Instances'
   required: ['data']
   properties:
     data:


Set timeout for submitlist

Set timeout for submitlist

--- client/components/SubmitList.coffee
+++ client/components/SubmitList.coffee
@@ -34,6 +34,9 @@
     @url: (props) ->
         if props?.myUser?._id
             "submits/#{props.myUser._id}/#{props.material._id}"
+
+    @timeout: () ->
+        20 * 1000

     openSubmit: (submit) ->
         (e) =>


myteam: add Delete Team link button

myteam: add Delete Team link button

--- client/home/lib/myteam/index.coffee
+++ client/home/lib/myteam/index.coffee
@@ -13,6 +13,7 @@
 toImmutable = require 'app/util/toImmutable'
 canSeeMembers = require 'app/util/canSeeMembers'
 isAdmin = require 'app/util/isAdmin'
+showError = require 'app/util/showError'

 SECTIONS =
   'Invite Using Slack' : HomeTeamConnectSlack
@@ -118,3 +119,17 @@

       @wrapper.addSubView headerize 'Teammates'
       @wrapper.addSubView @teammates = section 'Teammates'
+
+      @wrapper.addSubView new kd.CustomHTMLView
+        cssClass : 'delete-team'
+        partial : 'DELETE TEAM'
+        click : ->
+          partial = '

+              CAUTION! You are going to delete your team. You and your
+              team members will not be able to access this team again.
+              This action CANNOT be undone.
+

+
Please enter current password into the field below to continue:
'
+
+          TeamFlux.actions.deleteTeam(partial).catch (err) ->
+            showError err


cycleView: bugfix: call super in initialize

--- coffee/src/views/cycle_button_view.coffee
+++ coffee/src/views/cycle_button_view.coffee
@@ -33,6 +33,7 @@

   initialize: =>
+    super
     @buttonName = @options.buttonName ? ''
     @data = @options.data ? MissingParameterError 'CycleButton', 'data'


Remove title which is not commonly used, add inputName to use it in forms

--- components/inputs/KDMultipleChoice.coffee
+++ components/inputs/KDMultipleChoice.coffee
@@ -36,8 +36,8 @@
     @setValue options.defaultValue, no

   setDomElement:(cssClass)->
-    {title, labels, name} = @getOptions()
-    title = if title then "#{title}" else ''
+    {labels, name} = @getOptions()
+    @inputName = name

     labelItems = ""
     for label in labels
@@ -45,7 +45,6 @@

     @domElement = $ """

-        #{title}
         #{labelItems}

 """


Rename some tests

Rename some tests

--- spec/basics-spec.coffee
+++ spec/basics-spec.coffee
@@ -1,7 +1,7 @@
 jasmine.getFixtures().fixturesPath = 'base/build/spec/fixtures'
 jasmine.getJSONFixtures().fixturesPath = 'base/build/spec/fixtures'

-describe 'Phone basics', ->
+describe 'front.phone component', ->

 	it 'should be defined', ->
 		expect(vtex.phone).toBeDefined()


better is_positive_int

better is_positive_int

--- src/merkle/leaf.iced
+++ src/merkle/leaf.iced
@@ -16,10 +16,7 @@
 #--------------------------

 is_positive_int = (x) ->
-  return false unless typeof(x) is 'number'
-  return false unless Math.floor(x) is x
-  return false if x < 0
-  return true
+  return (typeof(x) is 'number') and (Math.floor(x) is x) and isFinite(x) and x >= 0

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


:bug: Fix specs

:bug: Fix specs

--- spec/helper-spec.coffee
+++ spec/helper-spec.coffee
@@ -31,19 +31,19 @@
           expect(text).toBe(testContents)
     it "throws if stderr is written to but wasn't expected", ->
       waitsForPromise ->
-        helpers.execNode("#{__dirname}/fixtures/stderr.js", []).catch (message) ->
-          expect(message).toBe('STDERR')
+        helpers.execNode("#{__dirname}/fixtures/stderr.js", []).catch (error) ->
+          expect(error.message).toBe('STDERR')
       waitsForPromise ->
-        helpers.exec("#{__dirname}/fixtures/stderr.sh", []).catch (message) ->
-          expect(message).toBe("STDERR\n")
+        helpers.exec("#{__dirname}/fixtures/stderr.sh", []).catch (error) ->
+          expect(error.message).toBe("STDERR\n")

     describe 'throwOnStdErr option', ->
       it 'throws unexpected error when set to true', ->
         gotError = false
         waitsForPromise ->
-          helpers.exec("#{__dirname}/fixtures/stderr.sh", [], throwOnStdErr: true).catch( (message) ->
+          helpers.exec("#{__dirname}/fixtures/stderr.sh", [], throwOnStdErr: true).catch( (error) ->
             gotError = true
-            expect(message).toBe("STDERR\n")
+            expect(error.message).toBe("STDERR\n")
           ).then ->
             expect(gotError).toBe(true)
       it 'suppresses unexpected errors when set to false', ->


tests: Enable MQTT

tests: Enable MQTT

--- spec/participant.coffee
+++ spec/participant.coffee
@@ -9,7 +9,7 @@

 # Note: most require running an external broker service
 transports =
-  #'MQTT': 'mqtt://localhost'
+  'MQTT': 'mqtt://localhost'
   'AMQP': 'amqp://localhost'

 transportTests = (g, address) ->


Remove unused locals in spec-helper

Remove unused locals in spec-helper

--- spec/spec-helper.coffee
+++ spec/spec-helper.coffee
@@ -28,9 +28,6 @@
 fixturePackagesPath = path.resolve(__dirname, './fixtures/packages')
 atom.packages.packageDirPaths.unshift(fixturePackagesPath)
 atom.keymaps.loadBundledKeymaps()
-keyBindingsToRestore = atom.keymaps.getKeyBindings()
-commandsToRestore = atom.commands.getSnapshot()
-styleElementsToRestore = atom.styles.getSnapshot()

 window.addEventListener 'core:close', -> window.close()


Sending HipChat Tower messages upon server death, I hope.

--- multicore.coffee
+++ multicore.coffee
@@ -2,13 +2,19 @@
 numCPUs = require('os').cpus().length

 if cluster.isMaster
-        for i in [0...numCPUs]
-                cluster.fork()
-        cluster.on 'exit', (worker, code, signal) ->
-                console.log 'worker ' + worker.id + ' died'
-                cluster.fork()
+  for i in [0...numCPUs]
+    cluster.fork()
+  cluster.on 'exit', (worker, code, signal) ->
+    message = "Worker #{worker.id} died! Heart attack takin' a dump."
+    console.log message
+    try
+      hipchat = require './server/hipchat'
+      hipchat.sendHipChatMessage(message, ['tower'], {papertrail: true})
+    catch error
+      console.log "Couldn't send HipChat message on server death:", error
+    cluster.fork()
 else
-        require('coffee-script')
-        require('coffee-script/register')
-        server = require('./server')
-        server.startServer()
+  require('coffee-script')
+  require('coffee-script/register')
+  server = require('./server')
+  server.startServer()


The Rock's in the building

The Rock's in the building

--- app/models/boys.coffee
+++ app/models/boys.coffee
@@ -66,7 +66,7 @@
 ######################################################
 ,
   # name: 'Mark'
-  mame: 'Jay'
+  name: 'Jay'
   number: '5558522'
   tnumber: '+16466815856'
   traits: ['park', 'hat', 'baseball']
@@ -195,4 +195,4 @@
   traits: ['mall', 'tie', 'icecream']
   place: "Crosstown Mall"
   image: 'http://upload.wikimedia.org/wikipedia/commons/thumb/7/74/Jeff_Merkley.jpg/473px-Jeff_Merkley.jpg'
-]+]


implement draft vs sent handling @TODO : need tests

--- server/controllers/messages.coffee
+++ server/controllers/messages.coffee
@@ -3,6 +3,7 @@
 {HttpError} = require '../utils/errors'
 Client      = require('request-json').JsonClient
 jsonpatch   = require 'fast-json-patch'
+nodemailer  = require 'nodemailer'
 Compiler    = require 'nodemailer/src/compiler'
 Promise     = require 'bluebird'
 htmlToText  = require 'html-to-text'
@@ -84,27 +85,67 @@
 # send a message through the DS
 module.exports.send = (req, res, next) ->

-    # @TODO : save message to Sent folder
     # @TODO : if message was a draft, delete it from Draft folder
     # @TODO : save draft into DS

-    if req.body.isDraft
-        draft  = new Compiler(req.body).compile()
-        stream = draft.createReadStream()
-        message = ''
-        stream.on 'data', (data) ->
-            message += data.toString()
-        stream.on 'error', (err) ->
-            console.error('Error', err)
-        stream.on 'end', ->
-            # @TODO : save draft into DS
-            res.send 200, message
-    else
-        dataSystem.post 'mail/', req.body, (dsErr, dsRes, dsBody) ->
-            if dsErr
-                res.send 500, dsBody
+    message = req.body
+
+    # convert base64 attachments to buffer
+    message.attachments.map (attachment) ->
+        filename: attachment.filename
+        contents: new Buffer attachment.content.split(",")[1], 'base64'
+
+    # @TODO : save attachments in the DS
+
+    # compile the message
+    rawMessage = new Compiler(mail).compile().build()
+
+    # find the account and draftbox
+    Account.findPromised message.accountID
+    .then (account) ->
+        Mailbox.findPromised account.draftMailbox
+        .then (draftBox) -> return [account, draftBox]
+
+    .spread (account, draftBox) ->
+
+        # remove the old version if necessary
+        removeOld = ->
+            uid = message.mailboxIDs[draftBox.id]
+            if uid then ImapProcess.remove account, draftBox, uid
+            else Promise.resolve()
+
+        # store the message in IMAP
+        createMessageInIMAP = ->
+            pDestBox = if message.isDraft then Promise.resolve draftBox
+            else Mailbox.findPromised account.sentMailbox
+
+            pDestBox.then (dest) ->
+                ImapProcess.append account, dest, rawMessage
+                .then (uidInDest) ->
+                    message.mailboxIDs = {}
+                    message.mailboxIDs[dest.id] = uidInDest
+
+        # create or update the message in the DS
+        saveMessage = ->
+            if message.id
+                new Message(id: message.id).updateAttributesPromised message
             else
-                res.send 200, dsBody
+                Message.create message
+
+
+        if message.isDraft
+            removeOld()
+            .then -> createMessageInIMAP()
+            .then -> saveMessage()
+
+        else
+            # send before deleting draft
+            account.sendMessagePromised message
+            .then (info) -> message.headers['message-id'] = info.messageId
+            .then -> removeOld()
+            .then -> createMessageInIMAP()
+            .then -> saveMessage()
+

 # search in the messages using the indexer
 module.exports.search = (req, res, next) ->


Send welcome gif to user private channel

--- scripts/welcome-to-zihub.coffee
+++ scripts/welcome-to-zihub.coffee
@@ -57,7 +57,7 @@
       #      robot.adapter.client.chat.postMessage(user.name, welcomeMsg(user.name))
       robot.messageRoom user.name, welcomeMsg(user.name)
       robot.messageRoom '#presentaciones', "Hola @#{user.name}"
-      welcomeGif(user, 'presentaciones')
+      welcomeGif(user, user.name)
       add_nicks user.name

   robot.respond /la bienvenida/i, (msg) ->


updating default text for certain question types

--- source/javascripts/src/model_xlform.coffee
+++ source/javascripts/src/model_xlform.coffee
@@ -528,14 +528,16 @@
       value: "Use the camera to record a video"
   audio:
     label:
-      value: "Use the camera to record a sound"
+      value: "Use the camera's microphone to record a sound"
   note:
     label:
       value: "This note can be read out loud"
   integer:
     label:
       value: "Enter a number"
-      _promptToChangeDefault: true
+  barcode:
+    hint:
+      value: "Use the camera to scan a barcode"
   decimal:
     label:
       value: "Enter a number"


Add privilege clear feature

Add privilege clear feature

--- src/privilege.coffee
+++ src/privilege.coffee
@@ -55,7 +55,11 @@
     delete table[who]
     robot.brain.set PRIVILEGE_TABLE_KEY, table

-  robot.respond /privilege/i, (msg)->
-    table = robot.brain.get(PRIVILEGE_TABLE_KEY) || {}
-    msg.send JSON.stringify(table)
+  robot.respond /privilege(:?\s([^\s]*))?/i, (msg)->
+    action = msg.match[1]?.trim().toLowerCase()
+    if action == 'clear'
+      robot.brain.set PRIVILEGE_TABLE_KEY, {}
+    else
+      table = robot.brain.get(PRIVILEGE_TABLE_KEY) || {}
+      msg.send JSON.stringify(table)


Slight refactoring

Slight refactoring

--- js/inner.coffee
+++ js/inner.coffee
@@ -659,23 +659,29 @@
   constructor: ->
     super

-    @concurrencyTitle = komp =>
-      "#{@concurrency} concurrent build" + (if @concurrency == 1 then "" else "s")
-
-    @projectsTitle = komp =>
-      "#{@projects} project" + (if @projects == 1 then "" else "s")
-
-    @concurrencyContent = komp =>
-      "With the #{@name} plan, we will test #{@concurrency} pushes at once, and we'll queue the rest. Larger teams, and teams who push very frequently, may need more concurrent builds for fast test results."
-
-    @projectsContent = komp =>
-      "With the #{@name} plan, we will run your tests on #{@projects} projects."
+    @actualParallelism = komp =>
+      Math.min(@parallelism(), @max_parallelism)

     @total = komp =>
       dollars = @price / 100
       increment = Math.round(0.4 * dollars)
-      actualParallelism = Math.min(@parallelism(), @max_parallelism)
-      dollars + ((actualParallelism - 1) * increment)
+      dollars + ((@actualParallelism() - 1) * increment)
+
+    @concurrencyTitle = komp =>
+      "#{@concurrency} concurrent build" + (if @concurrency == 1 then "" else "s")
+
+    @projectsTitle = komp =>
+      "#{@projects} project" + (if @projects == 1 then "" else "s")
+
+    @concurrencyContent = komp =>
+      "With the #{@name} plan, we will test #{@concurrency} pushes at once, and we'll queue the rest. Larger teams, and teams who push very frequently, may need more concurrent builds for fast test results."
+
+    @projectsContent = komp =>
+      "With the #{@name} plan, we will run your tests on #{@projects} projects."
+
+
+
+

 class Billing extends Obj


Server: minor redirect change

Server: minor redirect change

--- servers/lib/server/index.coffee
+++ servers/lib/server/index.coffee
@@ -607,7 +607,7 @@

   if isInAppRoute name
     if name is 'Develop'
-      return res.redirect 301, '/Terminal'
+      return res.redirect 301, '/IDE'

     if name in ['Activity']
       isLoggedIn req, res, (err, loggedIn, account)->


fix for color to css name function

fix for color to css name function

--- framer/Color.coffee
+++ framer/Color.coffee
@@ -67,7 +67,14 @@
 	toName: ->
 		if @_a is 0 then return "transparent"
 		if @_a < 1 then return false
-		return cssNames[rgbToHex(@_r, @_g, @_b, @)] or false
+		hex = rgbToHex(@_r, @_g, @_b, true)
+
+		for key in _.keys cssNames
+			value = cssNames[key]
+			if value == hex
+				return key
+
+		return false

 	lighten: (amount = 10) ->
 		hsl = @toHsl()


Server: marketing email subscription endpoint

--- servers/lib/server/index.coffee
+++ servers/lib/server/index.coffee
@@ -560,6 +560,24 @@
   return false

+app.post '/-/emails/subscribe', (req, res)->
+
+  Sendgrid            = require '../sendgrid'
+  {email, type, name} = req.body
+
+  return res.status(400).send 'not ok'  unless /@/.test email
+
+  type or= 'marketing'
+  name or= email.split('@')[0]
+
+  switch type
+    when 'all'       then Sendgrid.addToAllUsers  email, name
+    when 'marketing' then Sendgrid.addToMarketing email, name
+    else res.status(400).send 'not ok'
+
+  res.status(200).send 'ok'
+
+
 app.post '/Hackathon/Apply', (req, res, next)->

   {JWFGH} = koding.models


webserver: add /Impersonate endpoint

webserver: add /Impersonate endpoint

--- servers/lib/server/index.coffee
+++ servers/lib/server/index.coffee
@@ -420,6 +420,34 @@
     res.cookie 'clientId', info.replacementToken, path : '/'
     res.status(200).end()

+
+app.post "/Impersonate/:nickname", (req, res) ->
+  { JAccount, JSession } = koding.models
+  {nickname} = req.params
+
+  {clientId} = req.cookies
+
+  JSession.fetchSession clientId, (err, result)->
+    return res.status(400).end()  if err or not result
+
+    { username } = result.session
+    JAccount.one { "profile.nickname" : username }, (err, account) ->
+      return res.status(400).end()  if err or not account
+
+      unless account.can 'administer accounts'
+        return res.status(403).end()
+
+      JSession.createNewSession nickname, (err, session) ->
+        return res.status(400).send err.message  if err
+
+        JSession.remove {clientId}, (err) ->
+          console.error 'Could not remove session:', err  if err
+
+          res.cookie 'clientId', session.clientId, path : '/'  if session.clientId
+          res.clearCookie 'realtimeToken'
+          res.status(200).end()
+
+
 app.post "/:name?/Recover", (req, res) ->
   { JPasswordRecovery } = koding.models
   { email } = req.body


remove coma

remove coma

--- app/scripts/app.coffee
+++ app/scripts/app.coffee
@@ -88,7 +88,7 @@
             onReInit: if attrs.onReInit then scope.onReInit else undefined
             onSetPosition: if attrs.onSetPosition then scope.onSetPosition else undefined
             pauseOnHover: scope.pauseOnHover isnt "false"
-            responsive: scope.responsive or null,
+            responsive: scope.responsive or null
             rtl: scope.rtl is "true"
             slide: scope.slide or "div"
             slidesToShow: if scope.slidesToShow? then parseInt(scope.slidesToShow, 10) else 1


Reenable AB tests, if the tests have returned within 20ms of the page loading

--- js/outer.coffee
+++ js/outer.coffee
@@ -191,5 +191,10 @@
   $.getScript(renderContext.assetsRoot + "/js/vendor/selectivizr-1.0.2.js")
 # `!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0];if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src="//platform.twitter.com/widgets.js";fjs.parentNode.insertBefore(js,fjs);}}(document,"script","twitter-wjs");`

+
 # Run the application
-$ -> circle.run window.location.pathname.replace(/\/$/, '')
+$ ->
+  circle.run window.location.pathname.replace(/\/$/, '')
+  setTimeout(  # give KM a short window to run
+    -> window.too_late_for_kissmetrics = true
+    , 20)


Added _getNextLevel() method

Added _getNextLevel() method

--- app/shufflegrid.coffee
+++ app/shufflegrid.coffee
@@ -4,12 +4,16 @@
 module.exports = class ShuffleGrid
   constructor: (@levels, @random, @$container, @maxWidth, @maxHeight) ->
     @random ?= new Random()
-    @levelIndex = 0
+    @nextLevelIndex = 0

   init: (@completeCallback) ->
-    @loadLevel(@levels[@levelIndex])
+    @loadLevel(@_getNextLevel())
     @shuffle()
+
+
+  _getNextLevel: ->
+    @levels[@nextLevelIndex++]

   loadLevel: (@level) ->
@@ -103,12 +107,12 @@
     $img = $ "<img style='height: 100%; width: 100%;' src='#{@level.image}'/>"
     thisGrid = this
     $img.fadeIn 1000, ->
-      thisGrid.levelIndex++
-      if thisGrid.levelIndex>=thisGrid.levels.length
+      nextLevel = thisGrid._getNextLevel()
+      if nextLevel?
+        thisGrid.loadLevel(nextLevel)
+        thisGrid.shuffle()
+      else
         thisGrid.completeCallback()
-      else
-        thisGrid.loadLevel(thisGrid.levels[thisGrid.levelIndex])
-        thisGrid.shuffle()
     @$grid.prepend $img


Allow anonymous branches in 'silent mode'

--- lib/Choice.coffee
+++ lib/Choice.coffee
@@ -76,12 +76,17 @@
       throw new Error 'Cannot continue tree without external onSubtree'
     @onSubtree @, name, true, callback

-  branch: (name, callback = ->) ->
-    if typeof name isnt 'string'
+  branch: (name, callback = null, silent = false) ->
+    unless callback
+      callback = ->
+    if typeof name isnt 'string' and not silent
       throw new Error 'Branches cannot be anonymous'
     unless typeof @onBranch is 'function'
       throw new Error 'Cannot branch without external onBranch'
-    id = name.replace /-/g, '_'
+    @branches = 0 unless @branches
+    @branches++
+    id = name.replace /-/g, '_' if name
+    id = "#{@id}_b#{@branches}" unless name
     branch = @createChoice @source, id, name
     branch.state = State.PENDING
     branch.onBranch = @onBranch
@@ -93,7 +98,7 @@
       continue if key in ['path', 'id', 'aborted']
       branch.attributes[key] = val

-    @onBranch @, branch, callback
+    @onBranch @, branch, callback, silent

     branch


Fix typo

Fix typo

--- core/app/backbone/models/wheel.coffee
+++ core/app/backbone/models/wheel.coffee
@@ -60,16 +60,16 @@
       authority: authority
       opinion_types: new_opinion_types

-  turned_off_topinion_types: ->
+  turned_off_opinion_types: ->
     believe:    is_user_opinion: false
     disbelieve: is_user_opinion: false
     doubt:      is_user_opinion: false

   turnOffActiveOpinionType: (toggle_type) ->
-    @updateTo @get("authority"), @turned_off_topinion_types()
+    @updateTo @get("authority"), @turned_off_opinion_types()

   turnOnActiveOpinionType: (toggle_type) ->
-    new_opinion_types = @turned_off_topinion_types()
+    new_opinion_types = @turned_off_opinion_types()
     new_opinion_types[toggle_type].is_user_opinion = true

     @updateTo @get("authority"), new_opinion_types


Add `paper-input` snippet.

Add `paper-input` snippet.

--- snippets/paper-input-elements.cson
+++ snippets/paper-input-elements.cson
@@ -1,6 +1,11 @@
 # Paper Input components snippets

 ".text.html":
+  "paper-input":
+    "prefix": "paper-input"
+    "body": """
+    <paper-input label="${1:Input label}"></paper-input>
+    """
   "paper-input-container":
     "prefix": "paper-input-container"
     "body": """


Rename 'active-editor-path-change' to 'root-view:active-path-changed'

--- src/tree-view.coffee
+++ src/tree-view.coffee
@@ -61,7 +61,7 @@
         @selectActiveFile()
     @rootView.command 'tree-view:toggle', => @toggle()
     @rootView.command 'tree-view:reveal-active-file', => @revealActiveFile()
-    @rootView.on 'active-editor-path-change', => @selectActiveFile()
+    @rootView.on 'root-view:active-path-changed', => @selectActiveFile()
     @rootView.project.on 'path-change', => @updateRoot()
     @observeConfig 'core.hideGitIgnoredFiles', => @updateRoot()


Fix fill actions to set the indices correctly

--- js/inner.coffee
+++ js/inner.coffee
@@ -127,6 +127,7 @@
       @status() == "success"

     @failed = @komp => @status == "failed" or @status == "timedout"
+    @infrastructure_fail = @komp => @status == "infrastructure_fail"

     # Expand failing actions
     @minimize = @komp =>
@@ -186,13 +187,13 @@
     actions: []

   constructor: (json) ->
-    json.actions = if json.actions then (new ActionLog(j) for j in json.actions) else []
+    json.actions = if json.actions? then (new ActionLog(j) for j in json.actions) else []
     super json

 class Build extends HasUrl
   constructor: (json) ->

-    json.steps = if json.steps then (new Step(j) for j in json.steps) else []
+    json.steps = if json.steps? then (new Step(j) for j in json.steps) else []

     super json,
       body: null
@@ -366,10 +367,9 @@

   fillActions: (step, index) =>
     # fills up steps and actions such that step and index are valid
-
-    for i in [0..step-1]
+    for i in [0..step]
       if not @steps()[i]?
-        @steps.setIndex(step, new Step({}))
+        @steps.setIndex(i, new Step({}))

     # actions can arrive out of order when doing parallel. Fill up the other indices so knockout doesn't bitch
     for i in [0..index-1]


Make the read-api return maps that always contain the commit info keys, (as opposed to not containing the key when nil). Make the UI not explode when those keys are missing.

--- js/inner.coffee
+++ js/inner.coffee
@@ -163,7 +163,26 @@
   constructor: (json) ->
     # make the actionlogs observable
     json.action_logs = (new ActionLog(j) for j in json.action_logs) if json.action_logs
-    super json, {}, ["build_num", "status", "committer_name", "committer_email", "why", "user", "job_name", "branch", "vcs_revision", "start_time", "build_time_millis"]
+    super json,
+       body: null
+       subject: null
+       committer_name: null
+       committer_email: null
+       committer_date: null
+       author_name: null
+       author_email: null
+       author_date: null,
+       ["build_num"
+        "status"
+        "committer_name"
+        "committer_email"
+        "why"
+        "user"
+        "job_name"
+        "branch"
+        "vcs_revision"
+        "start_time"
+        "build_time_millis"]

     @url = @komp =>
       "#{@project_path()}/#{@build_num}"


removed unnecessary code

removed unnecessary code

--- src/ui-scroll.coffee
+++ src/ui-scroll.coffee
@@ -27,11 +27,13 @@
 	'$log', '$injector', '$rootScope', '$timeout', '$q', '$parse'
 	(console, $injector, $rootScope, $timeout, $q, $parse) ->

+		log = console.debug || console.log
+
 		# Element manipulation routines

 		insertElement =
 			(newElement, previousElement) ->
-				previousElement.after.apply(previousElement, [newElement])
+				previousElement.after newElement
 				[]

 		removeElement = (wrapper) ->
@@ -67,9 +69,6 @@
 						wrapper.scope.$destroy()
 					]

-
-		# buffer
-
 		Buffer = (itemName, $scope, linker)->

 			buffer = Object.create Array.prototype
@@ -120,8 +119,6 @@
 		terminal: true

 		compile: (elementTemplate, attr, compileLinker) ->
-
-			log = console.debug || console.log

 			unless match = attr.uiScroll.match(/^\s*(\w+)\s+in\s+([\w\.]+)\s*$/)
 				throw new Error 'Expected uiScroll in form of \'_item_ in _datasource_\' but got \'' + $attr.uiScroll + '\''


[atom-reason] Fix switch snippet

[atom-reason] Fix switch snippet

--- editorSupport/language-reason/snippets/reason.cson
+++ editorSupport/language-reason/snippets/reason.cson
@@ -47,8 +47,7 @@
   "Reason: Switch Pattern Match":
     prefix: "sw"
     body: """
-       switch expr {
+       switch ${1:expression} {
          | ${2:firstCase} => ${3:result}
        }
     """
-


remove leftover console.log

remove leftover console.log

--- spec/title-bar-element-spec.coffee
+++ spec/title-bar-element-spec.coffee
@@ -15,5 +15,4 @@
     document.title = 'new-title'
     element.updateTitle()

-    console.log element.querySelector('.title').textContent
     expect(element.querySelector('.title').textContent).toBe 'new-title'


:art:

:art:

--- spec/tree-view-package-spec.coffee
+++ spec/tree-view-package-spec.coffee
@@ -2556,9 +2556,11 @@
           expect(atom.workspace.getModalPanels().length).toBe(0)

     describe "tree-view:remove", ->
+      beforeEach ->
+        jasmine.attachToDOM(workspaceElement)
+
       it "won't remove the root directory", ->
         spyOn(atom, 'confirm')
-        jasmine.attachToDOM(workspaceElement)
         treeView.focus()
         root1.dispatchEvent(new MouseEvent('click', {bubbles: true, detail: 1}))
         atom.commands.dispatch(treeView.element, 'tree-view:remove')
@@ -2594,7 +2596,6 @@
           expect(fs.existsSync(filePath)).toBe(false)

       it "shows a notification on failure", ->
-        jasmine.attachToDOM(workspaceElement)
         atom.notifications.clear()

         fileView.dispatchEvent(new MouseEvent('click', {bubbles: true, detail: 1}))
@@ -2615,7 +2616,6 @@
       it "does nothing when no file is selected", ->
         atom.notifications.clear()

-        jasmine.attachToDOM(workspaceElement)
         treeView.focus()
         treeView.deselect()
         atom.commands.dispatch(treeView.element, 'tree-view:remove')
@@ -2625,8 +2625,6 @@

       describe "when a directory is removed", ->
         it "closes editors with filepaths belonging to the removed folder", ->
-          jasmine.attachToDOM(workspaceElement)
-
           waitForWorkspaceOpenEvent ->
             atom.workspace.open(filePath2)

@@ -2646,8 +2644,6 @@
             expect(openFilePaths).toEqual([])

         it "does not close modified editors with filepaths belonging to the removed folder", ->
-          jasmine.attachToDOM(workspaceElement)
-
           waitForWorkspaceOpenEvent ->
             atom.workspace.open(filePath2)

@@ -2674,8 +2670,6 @@
           fs.makeTreeSync(dirPath20)
           fs.writeFileSync(filePath20, "doesn't matter 20")

-          jasmine.attachToDOM(workspaceElement)
-
           waitForWorkspaceOpenEvent ->
             atom.workspace.open(filePath2)

@@ -2698,8 +2692,6 @@
             expect(openFilePaths).toEqual([filePath20])

         it "does not error when Untitled editors are also open (regresssion)", ->
-          jasmine.attachToDOM(workspaceElement)
-
           waitForWorkspaceOpenEvent ->
             atom.workspace.open(filePath2)

@@ -2724,8 +2716,6 @@
             expect(openFilePaths).toEqual([undefined])

         it "focuses the directory's parent folder", ->
-          jasmine.attachToDOM(workspaceElement)
-
           dirView2.dispatchEvent(new MouseEvent('click', {bubbles: true, detail: 1}))
           treeView.focus()

@@ -2736,8 +2726,6 @@

       describe "when a file is removed", ->
         it "closes editors with filepaths belonging to the removed file", ->
-          jasmine.attachToDOM(workspaceElement)
-
           waitForWorkspaceOpenEvent ->
             atom.workspace.open(filePath2)

@@ -2754,8 +2742,6 @@
             expect(openFilePaths).toEqual([])

         it "does not close editors that have been modified", ->
-          jasmine.attachToDOM(workspaceElement)
-
           waitForWorkspaceOpenEvent ->
             atom.workspace.open(filePath2)

@@ -2776,7 +2762,6 @@
         it "does not close editors with filepaths that begin with the removed file", ->
           filePath2Copy = path.join(dirPath2, 'test-file2.txt0')
           fs.writeFileSync(filePath2Copy, "doesn't matter 2 copy")
-          jasmine.attachToDOM(workspaceElement)

           waitForWorkspaceOpenEvent ->
             atom.workspace.open(filePath2Copy)
@@ -2794,8 +2779,6 @@
             expect(openFilePaths).toEqual([filePath2Copy])

         it "focuses the file's parent folder", ->
-          jasmine.attachToDOM(workspaceElement)
-
           fileView2.dispatchEvent(new MouseEvent('click', {bubbles: true, detail: 1}))
           treeView.focus()

@@ -2809,7 +2792,6 @@
         it "does not error when the selected entries form a parent/child relationship", ->
           # If dir1 and dir1/file1 are both selected for deletion,
           # and dir1 is deleted first, do not error when attempting to delete dir1/file1
-          jasmine.attachToDOM(workspaceElement)
           atom.notifications.clear()

           fileView.dispatchEvent(new MouseEvent('click', {bubbles: true, detail: 1}))
@@ -2837,7 +2819,6 @@
         it "does not error", ->
           # If the file is marked for deletion but has already been deleted
           # outside of Atom by the time the deletion is confirmed, do not error
-          jasmine.attachToDOM(workspaceElement)
           atom.notifications.clear()

           fileView.dispatchEvent(new MouseEvent('click', {bubbles: true, detail: 1}))


removed console.log call

removed console.log call

--- src/assets.coffee
+++ src/assets.coffee
@@ -319,7 +319,6 @@
       options.paths = [path.dirname(sourcePath)].concat(options.paths)

       callback = (err, tree) ->
-        console.log "FEhler", err if err
         throw err if err
         result = tree.toCSS({})


OHM-277: Remove unecessary functionality from metadata API

--- src/api/metadata.coffee
+++ src/api/metadata.coffee
@@ -28,15 +28,6 @@
   try
     exportObject = {}
     params = this.request.query
-
-    # Return selected documents from selected collections for export
-    if params.selected == 'true'
-      for key of params
-        if collections[key]
-          ids = params[key]
-          ids = [ids] if typeof ids is 'string'
-          exportObject[key] = yield collections[key].find({ _id: { $in: ids } }).exec()
-      return this.body = [exportObject]

     # Return all documents from all collections for export
     for key of collections


Ommit .json format from url

Ommit .json format from url

--- src/api/settings.coffee
+++ src/api/settings.coffee
@@ -31,7 +31,7 @@
       completedEvent: 'task.{collectionUUID}/{moduleUUID}.receive.fetchByModule'

     'user.send.statusUpdate':
-      url: 'auth/status.json'
+      url: 'auth/status'
       method: 'GET'
       useCredentials: true
       failureEvent: 'user.recieve.loadFailure'


Reflect renamed field dropped -> dbs_dropped in web UI code. Reviewd by @deontologician. Closes #3532.

--- admin/static/coffee/modals.coffee
+++ admin/static/coffee/modals.coffee
@@ -98,10 +98,10 @@
                 if (err)
                     @on_error(err)
                 else
-                    if result?.dropped is 1
+                    if result?.dbs_dropped is 1
                         @on_success()
                     else
-                        @on_error(new Error("The return result was not `{dropped: 1}`"))
+                        @on_error(new Error("The return result was not `{dbs_dropped: 1}`"))

         on_success: (response) =>
             super()


Michael broke the navbar active style. Fixing.

--- admin/static/coffee/navbar.coffee
+++ admin/static/coffee/navbar.coffee
@@ -9,7 +9,7 @@
     initialize: ->
         log_initial '(initializing) NavBarView'
         # rerender when route changes
-        $(window.app_events).on "on_ready", =>
+        $(window.app_events).on "router_ready", =>
             # Render every time the route changes
             window.app.on "all", @render


return /admin route for master branch

return /admin route for master branch

--- src/server/controllers/index.coffee
+++ src/server/controllers/index.coffee
@@ -28,6 +28,6 @@
       )
     )

-#    app.get('/admin', (req, res)->
-#      res.render('admin_index', {})
-#    )
+    app.get('/admin', (req, res)->
+      res.render('admin_index', {})
+    )


Added oniguruma link

Added oniguruma link

--- grammars/latex.cson
+++ grammars/latex.cson
@@ -5,6 +5,8 @@
 ]

 limitLineLength: false
+
+# Atom uses the following regex engine -> https://github.com/kkos/oniguruma/blob/master/doc/RE

 # Rules to be implemented. Please add to repository and link to a 'meta' to include.
 # The following rules correspond to the ten special latex characters


Fix plain syntax & numeric constants & make groups atomic

--- grammars/latex.cson
+++ grammars/latex.cson
@@ -5,6 +5,9 @@
 fileTypes: [
   'bbx'
   'cbx'
+  'lbx'
+  'dbx'
+  'def'
   'cls'
   'lco'
   'sty'
@@ -594,18 +597,40 @@
   syntaxPlain: {
     comment: 'This is bare minimum that should work in most cases. It does not attempt to match pairs of anything, or specific constructions; just control sequences'
     patterns: [
-      { include: '#controlSymbol' }
-      { include: '#controlWord' }
       { include: '#lineComment' }
+      { include: '#metaPrimitives' }
+      { include: '#metaAmpersand' }
+      { include: '#metaTilde' }
+
+      { include: '#metaHashtag' }
+      { include: '#doubleCaret' }
+      { include: '#constants' }
+      { include: '#numericConstants' }
       {
         comment: 'Does not look for matching, just shows it is there'
-        match: '(\\$\\$)|(\\$)|(\\{)|(\\})'
+        match: '(\\$\\$)|(\\$)'
         captures:
           1: name: 'markup.other.math.display.latex'
           2: name: 'markup.other.math.inline.latex'
-          3: name: 'punctuation.definition.group.begin.latex'
-          4: name: 'punctuation.definition.group.end.latex'
-      }
+      }
+      {
+        comment: 'Does not look for matching, just shows it is there'
+        match: '(\\\\\\()|(\\\\\\))|(\\\\\\[)|(\\\\\\])'
+        captures:
+          1: name: 'markup.other.math.inline.begin.latex'
+          2: name: 'markup.other.math.inline.end.latex'
+          3: name: 'markup.other.math.display.begin.latex'
+          4: name: 'markup.other.math.display.end.latex'
+      }
+      {
+        match: '(\\{)|(\\})'
+        captures:
+          1: name: 'punctuation.definition.group.begin.latex'
+          2: name: 'punctuation.definition.group.end.latex'
+      }
+      { include: '#controlSymbol' }
+      { include: '#controlWord' }
+
     ]
   }
   documentclass: {
@@ -1199,14 +1224,14 @@
   }
   constants: {
     comment: 'Taken from language-latex'
-    match: '(\\\\)(text(s(terling|ixoldstyle|urd|e(ction|venoldstyle|rvicemark))|yen|n(ineoldstyle|umero|aira)|c(ircledP|o(py(left|right)|lonmonetary)|urrency|e(nt(oldstyle)?|lsius))|t(hree(superior|oldstyle|quarters(emdash)?)|i(ldelow|mes)|w(o(superior|oldstyle)|elveudash)|rademark)|interrobang(down)?|zerooldstyle|o(hm|ne(superior|half|oldstyle|quarter)|penbullet|rd(feminine|masculine))|d(i(scount|ed|v(orced)?)|o(ng|wnarrow|llar(oldstyle)?)|egree|agger(dbl)?|blhyphen(char)?)|uparrow|p(ilcrow|e(so|r(t(housand|enthousand)|iodcentered))|aragraph|m)|e(stimated|ightoldstyle|uro)|quotes(traight(dblbase|base)|ingle)|f(iveoldstyle|ouroldstyle|lorin|ractionsolidus)|won|l(not|ira|e(ftarrow|af)|quill|angle|brackdbl)|a(s(cii(caron|dieresis|acute|grave|macron|breve)|teriskcentered)|cutedbl)|r(ightarrow|e(cipe|ferencemark|gistered)|quill|angle|brackdbl)|g(uarani|ravedbl)|m(ho|inus|u(sicalnote)?|arried)|b(igcircle|orn|ullet|lank|a(ht|rdbl)|rokenbar)))(?![a-zA-Z@])'
+    match: '(\\\\)(?>text(?>s(?>terling|ixoldstyle|urd|e(?>ction|venoldstyle|rvicemark))|yen|n(?>ineoldstyle|umero|aira)|c(?>ircledP|o(?>py(?>left|right)|lonmonetary)|urrency|e(?>nt(?>oldstyle)?|lsius))|t(?>hree(?>superior|oldstyle|quarters(?>emdash)?)|i(?>ldelow|mes)|w(?>o(?>superior|oldstyle)|elveudash)|rademark)|interrobang(?>down)?|zerooldstyle|o(?>hm|ne(?>superior|half|oldstyle|quarter)|penbullet|rd(?>feminine|masculine))|d(?>i(?>scount|ed|v(?>orced)?)|o(?>ng|wnarrow|llar(?>oldstyle)?)|egree|agger(?>dbl)?|blhyphen(?>char)?)|uparrow|p(?>ilcrow|e(?>so|r(?>t(?>housand|enthousand)|iodcentered))|aragraph|m)|e(?>stimated|ightoldstyle|uro)|quotes(?>traight(?>dblbase|base)|ingle)|f(?>iveoldstyle|ouroldstyle|lorin|ractionsolidus)|won|l(?>not|ira|e(?>ftarrow|af)|quill|angle|brackdbl)|a(?>s(?>cii(?>caron|dieresis|acute|grave|macron|breve)|teriskcentered)|cutedbl)|r(?>ightarrow|e(?>cipe|ferencemark|gistered)|quill|angle|brackdbl)|g(?>uarani|ravedbl)|m(?>ho|inus|u(?>sicalnote)?|arried)|b(?>igcircle|orn|ullet|lank|a(?>ht|rdbl)|rokenbar)))(?!>[a-zA-Z@])'
     captures:
       0: name: 'constant.character.latex'
       1: name: 'punctuation.definition.constant.latex'
   }
   numericConstants: {
     comment: 'To match numbers of form 12, 1.2, etc.'
-    match: '(\\d*\\.\\d+|(\\d+\\.)?\\d*)'
+    match: '(\\d*\\.\\d+|(\\d+\\.)?\\d+)'
     name: 'constant.numeric.math.latex'
   }
   tikzOptions: {


fix global macro } coloring inside for loop Now } in a for loop is only colored as error when there's a space between text before } and the }.

--- grammars/stata.cson
+++ grammars/stata.cson
@@ -316,7 +316,7 @@
         ]
     'comment': 'It\'s a complicated regex so that it\'ll alert you if you have text on the same line as the }'
     'comment': 'correct with nothing else on the line but whitespace; before and after; before; after; correct'
-    'end': '^\\s*(\\})\\s*$|^\\s*([^\\*"\\}]+)\\s*(\\})\\s*([^\\*"\\}/\\n]+)|^\\s*([^"\\*\\}]+)\\s*(\\})|\\s*(\\})\\s*([^"\\*\\}/\\n]+)|(\\})$'
+    'end': '^\\s*(\\})\\s*$|^\\s*([^\\*"\\}]+)\\s+(\\})\\s*([^\\*"\\}/\\n]+)|^\\s*([^"\\*\\}]+)\\s+(\\})|\\s*(\\})\\s*([^"\\*\\}/\\n]+)|(\\})$'
     'endCaptures':
       '1':
         'name': 'keyword.control.block.end.stata'
@@ -822,11 +822,33 @@
   'macro-global':
     'patterns': [
       {
-        'begin': '(\\$)(\\{)?'
+        'begin': '(\\$)(\\{)'
         'beginCaptures':
           '0':
             'name': 'punctuation.definition.string.begin.stata'
-        'end': '(\\})|(?=\\"|\\s|\\n|/|,)'
+        'end': '(\\})'
+        'endCaptures':
+          '1':
+            'name': 'punctuation.definition.string.end.stata'
+        'patterns': [
+          {
+            'include': '#macro-local'
+          }
+          {
+            'include': '#macro-global'
+          }
+          {
+            'match': '[^`\'\\$\\{\\}\\"\\s\\n/,]+'
+            'name': 'entity.name.type.class.stata'
+          }
+        ]
+      }
+      {
+        'begin': '(\\$)'
+        'beginCaptures':
+          '0':
+            'name': 'punctuation.definition.string.begin.stata'
+        'end': '(?=\\"|\\s|\\n|/|,)'
         'endCaptures':
           '1':
             'name': 'punctuation.definition.string.end.stata'


Change } to keyword.control.block.*end*.stata to activate shading

--- grammars/stata.cson
+++ grammars/stata.cson
@@ -286,7 +286,7 @@
     'end': '(\\})'
     'endCaptures':
       '0':
-        'name': 'keyword.control.block.begin.stata'
+        'name': 'keyword.control.block.end.stata'
     'patterns': [
       {
         'include': '$self'


Make headline API test order too

Make headline API test order too

--- server/test/integration/api_indicator.coffee
+++ server/test/integration/api_indicator.coffee
@@ -465,7 +465,7 @@
   )
 )

-test('GET /:id/headlines returns the 5 most recent headlines', (done) ->
+test('GET /:id/headlines returns the 5 most recent headlines in descending order', (done) ->
   indicatorData = [
     {
       "year": 2000,
@@ -480,11 +480,11 @@
       "value": 4
       "text": 'Fair'
     }, {
-      "year": 2003,
+      "year": 2004,
       "value": 4
       "text": 'Fair'
     }, {
-      "year": 2004,
+      "year": 2003,
       "value": 4
       "text": 'Fair'
     }
@@ -532,6 +532,9 @@

       assert.lengthOf headlines, 5, "Expected 5 headlines to be returned"

+      mostRecentHeadline = headlines[0]
+      assert.strictEqual mostRecentHeadline.year, 2004, "Expected the most recent headline first"
+
       done()
     catch e
       done(e)


kernel: Sign complete and shutdown messages

--- lib/kernel.coffee
+++ lib/kernel.coffee
@@ -153,38 +153,32 @@
         @_execute(code, requestId, onResults)

     complete: (code, onResults) ->
+        console.log "sending completion"
+
         requestId = "complete_" + uuid.v4()

         column = code.length

-        console.log "sending completion"
-        header = JSON.stringify({
-                msg_id: requestId,
-                username: "",
-                session: "00000000-0000-0000-0000-000000000000",
-                msg_type: "complete_request",
+        header =
+                msg_id: requestId
+                username: ""
+                session: "00000000-0000-0000-0000-000000000000"
+                msg_type: "complete_request"
                 version: "5.0"
-            })
-
-        contents = JSON.stringify({
+
+        content =
                 code: code
                 text: code
                 line: code
                 cursor_pos: column
-            })
-
-        message =  [
-                '<IDS|MSG>',
-                '',
-                header,
-                '{}',
-                '{}',
-                contents
-            ]
-        console.log message
+
+        message =
+                header: header
+                content: content

         @executionCallbacks[requestId] = onResults
-        @shellSocket.send message
+
+        @_send message, @shellSocket

     addWatchCallback: (watchCallback) ->
         @watchCallbacks.push(watchCallback)
@@ -312,30 +306,26 @@
         return msgObject

     destroy: ->
+        console.log "sending shutdown"
+
         requestId = uuid.v4()

-        console.log "sending shutdown"
-        header = JSON.stringify({
+        header =
                 msg_id: requestId,
                 username: "",
                 session: 0,
                 msg_type: "shutdown_request",
                 version: "5.0"
-            })
-
-        contents = JSON.stringify({
+
+        content =
                 restart: false
-            })
-
-        message =  [
-                '<IDS|MSG>',
-                '',
-                header,
-                '{}',
-                '{}',
-                contents
-            ]
-        @shellSocket.send message
+
+        message =
+                header: header
+                content: content
+
+        @_send message, @shellSocket
+
         @shellSocket.close()
         @ioSocket.close()


fix logged-in & logged-out appear at same time

--- src/chaplin/views/layout.coffee
+++ src/chaplin/views/layout.coffee
@@ -93,7 +93,7 @@
     # Logged-in / logged-out classes for the body element
     # ---------------------------------------------------

-    updateLoginClasses: (loggedIn) ->
+    updateLoginClasses: (loggedIn = no) ->
       $(document.body)
         .toggleClass('logged-out', not loggedIn)
         .toggleClass('logged-in', loggedIn)


jupyter: just a tiny waste of time...

jupyter: just a tiny waste of time...

--- src/smc-webapp/jupyter/jupyter.cjsx
+++ src/smc-webapp/jupyter/jupyter.cjsx
@@ -28,6 +28,23 @@
 ###
 The React components
 ###
+
+InputEditor = rclass
+    propTypes :
+        value    : rtypes.string
+        style    : rtypes.object
+        onChange : rtypes.func.isRequired
+
+    handle_change: (e) ->
+        @props.onChange(e.target.value)
+
+    render : ->
+        <FormControl
+            componentClass = 'textarea'
+            value          = {@props.value}
+            style          = {@props.style}
+            onChange       = {@handle_change}
+        />

 JupyterEditor = rclass ({name}) ->
     propTypes :
@@ -296,9 +313,11 @@

                 In [{cell.get('number') ? '*'}]:

-

-                {cell.get('input') ? ''}
-

+            <InputEditor
+                value = {cell.get('input')}
+                style = {width:'100%', backgroundColor: '#f7f7f7', fontFamily: 'monospace'}
+                onChange = {(value) => @props.actions.set_cell_input(cell.get('id'), value)}
+            />

     render_output_number: (n) ->
@@ -363,6 +382,13 @@
     set_error: (err) =>
         @setState
             error : err
+
+    set_cell_input: (id, value) =>
+        # TODO: insanely stupid/slow -- just for proof of concept
+        @syncdb.set
+            type  : 'cell'
+            id    : id
+            input : value

     _syncdb_change: =>
         # TODO: this is not efficient!


webapp/project files: when opening a non-existing directory, the smc-ls listing works fine but returns an error. handle that error later, not as an error of calling it (previously, that would call it in a loop for 4 minutes (the timeout))

--- src/smc-webapp/project_store.coffee
+++ src/smc-webapp/project_store.coffee
@@ -1538,7 +1538,8 @@
         method = salvus_client.project_directory_listing
     else
         method = salvus_client.public_project_directory_listing
-    listing = undefined
+    listing     = undefined
+    listing_err = undefined
     f = (cb) ->
         method
             project_id : opts.project_id
@@ -1547,13 +1548,15 @@
             hidden     : opts.hidden
             timeout    : 20
             cb         : (err, x) ->
-                listing = x
-                cb(err)
+                listing     = x
+                listing_err = err
+                # the call itself is successful, even when it returns an error
+                cb()

     misc.retry_until_success
         f        : f
         max_time : opts.max_time_s * 1000
         #log      : console.log
         cb       : (err) ->
-            opts.cb(err, listing)
-
+            opts.cb(err ? listing_err, listing)
+


:art: Beautify linter constructor a bit

:art: Beautify linter constructor a bit

--- lib/linter.coffee
+++ lib/linter.coffee
@@ -1,7 +1,10 @@
 Path = require 'path'
 {CompositeDisposable, Emitter} = require 'atom'
 LinterViews = require './linter-views'
+MessageRegistry = require './message-registry'
+EditorRegistry = require './editor-registry'
 EditorLinter = require './editor-linter'
+LinterRegistry = require './linter-registry'
 Helpers = require './helpers'
 Commands = require './commands'

@@ -15,9 +18,9 @@

     # Private Stuff
     @emitter = new Emitter
-    @linters = new (require('./linter-registry'))()
-    @editors = new (require('./editor-registry'))()
-    @messages = new (require('./message-registry'))()
+    @linters = new LinterRegistry
+    @editors = new EditorRegistry
+    @messages = new MessageRegistry()
     @views = new LinterViews(state.scope, @editors)
     @commands = new Commands(this)


Multiple layers were not being added to the site stack.

--- lib/loader.coffee
+++ lib/loader.coffee
@@ -103,7 +103,7 @@
       if newname
         newname= normalize(newname)
         stack.unshift(newname)
-        siteLookup(newname)
+        siteLookup(req,newname)
       else
         return stack
     return if siteLookupRoot?


Fix Undo/Redo keyboard shortcuts working across browsers [#93910556]

--- src/code/mixins/app-view.coffee
+++ src/code/mixins/app-view.coffee
@@ -68,7 +68,7 @@
     ($ window).on 'keyup', (e) =>
       y = e.keyCode is 89
       z = e.keyCode is 90
-      if e.ctrlKey and (y or z)
+      if (e.ctrlKey or e.metaKey) and (y or z)
         e.preventDefault()
         @props.linkManager.redo() if y
         @props.linkManager.undo() if z


Add URI decoding to file names.

Add URI decoding to file names.

--- src/coffee/dropship_file.coffee
+++ src/coffee/dropship_file.coffee
@@ -105,7 +105,7 @@
     basename = @url.split('#', 2)[0].split('?', 2)[0]
     while basename.substring(basename.length - 1) == '/'
       basename = basename.substring 0, basename.length - 1
-    basename.substring basename.lastIndexOf('/') + 1
+    decodeURIComponent(basename.substring basename.lastIndexOf('/') + 1)

   # Called while the file is downloading.
   #


fix #96

fix #96

--- lib/motion.coffee
+++ lib/motion.coffee
@@ -179,10 +179,15 @@

   moveCursor: (cursor) ->
     isBufferRowWise = @editor.isSoftWrapped() and @isMode('visual', 'linewise')
+    vimLastBufferRow = null
     @countTimes =>
       if isBufferRowWise
-        point = cursor.getBufferPosition().translate([@amount, 0])
-        cursor.setBufferPosition(point)
+        vimLastBufferRow ?= getVimLastBufferRow(@editor)
+        row = cursor.getBufferRow() + @amount
+        if row <= vimLastBufferRow
+          column = cursor.goalColumn or cursor.getBufferColumn()
+          cursor.setBufferPosition([row, column])
+          cursor.goalColumn = column
       else
         @move(cursor)


Initial thoughts

Initial thoughts

--- lib/redeye.coffee
+++ lib/redeye.coffee
@@ -56,14 +56,25 @@
       @workers[key]?.resume()
     @resume.subscribe _('resume').namespace(@options.db_index)

-    @control.on 'message', (channel, action) => @perform action
+    @control.on 'message', (channel, msg) => @perform msg
     @control.subscribe _('control').namespace(@options.db_index)

   # React to a control message sent by the dispatcher
-  perform: (action) ->
+  perform: (msg) ->
+    action, args... = msg.split consts.key_sep
     switch action
       when 'quit' then @quit()
       when 'reset' then @reset()
+      when 'cycle' then @cycle_detected args...
+
+  # The dispatcher is telling us the given key is part of a cycle. If it's one
+  # of ours, cause the worker to re-run, but throwing an error from the @get that
+  # caused the cycle. On the plus side, we can assume that all the worker's non-
+  # cycled dependencies have been met now.
+  cycle_detected: (key, dependencies...) ->
+    if worker = @workers[key]
+      for dep in dependencies
+        worker.cycle[dependency] = true

   # Run the work queue, calling the given callback on completion
   run: (@callback) ->
@@ -179,10 +190,12 @@
     if @sticky[key]
       @sticky[key]
     else if @stage < @last_stage
-      # TODO: move the @build and @sticky[]= into @get_deps
       value = @build @cache[key], opts.as
       @sticky[key] = value if opts.sticky
       value
+    else if @cycle[key]
+      @cycle[key] = false
+      throw new CycleError
     else
       @deps.push key
       @blank()
@@ -289,6 +302,7 @@
     @emitted = false
     @search = null
     @is_async = false
+    @cycle = {}
     Worker.clear_callback?.apply this

   # If the caught error is from a `@for_reals`, then try to resolve


removed uneccesary statement

removed uneccesary statement

--- lib/script.coffee
+++ lib/script.coffee
@@ -13,7 +13,6 @@
   deactivate: ->
     @scriptView.close()
     @scriptOptionsView.close()
-    @scriptOptions = null

   serialize: ->
     scriptViewState: @scriptView.serialize()


Update schemas

Update schemas

--- grunt/sqlite.coffee
+++ grunt/sqlite.coffee
@@ -39,6 +39,7 @@

             CREATE TABLE tFile (
                 id INTEGER PRIMARY KEY AUTOINCREMENT,
+                hash TEXT NOT NULL,
                 locator TEXT,
                 name TEXT,
                 description TEXT,
@@ -47,11 +48,11 @@
             );

             CREATE TABLE tFileTagRel (
-                id INTEGER PRIMARY KEY AUTOINCREMENT,
-                file INTEGER,
-                tag INTEGER,
+                file INTEGER NOT NULL,
+                tag INTEGER NOT NULL,
                 FOREIGN KEY (file) REFERENCES tFile (id) ON DELETE CASCADE,
-                FOREIGN KEY (tag) REFERENCES tTag (id) ON DELETE CASCADE
+                FOREIGN KEY (tag) REFERENCES tTag (id) ON DELETE CASCADE,
+                PRIMARY KEY (file, tag)
             );
         '''


fixed incorrect variable name

fixed incorrect variable name

--- lib/server.coffee
+++ lib/server.coffee
@@ -1,10 +1,10 @@
 @AntiSearchSource.allow = (collectionName, allowRules) ->
-  check currentAllowRule,
+  check allowRules,
     maxLimit: Match.Optional(Number)
     securityCheck: Match.Optional(Function)
     allowedFields: Match.Optional([String])
     queryTransform: Match.Optional(Function)
-
+
   @_allowRules[collectionName] = allowRules


Use arguments instead of disposables... splat

--- src/composite-disposable.coffee
+++ src/composite-disposable.coffee
@@ -52,9 +52,9 @@
   #
   # * `disposable` {Disposable} instance or any object with a `.dispose()`
   #   method.
-  add: (disposables...) ->
+  add: ->
     unless @disposed
-      @disposables.add(disposable) for disposable in disposables
+      @disposables.add(disposable) for disposable in arguments
     return

   # Public: Remove a previously added disposable.


Add version to client exceptions

Add version to client exceptions

--- angular/core/services/exception_handler.coffee
+++ angular/core/services/exception_handler.coffee
@@ -20,5 +20,6 @@
     client.notify
       error: exception,
       params:
+        version:         AppConfig.version
         current_user_id: AppConfig.currentUserId
         angular_cause: cause


Camera refactoring

Camera refactoring

--- src/camera_shake.coffee
+++ src/camera_shake.coffee
@@ -2,6 +2,10 @@
   Object.reverseMerge I,
     shakeIntensity: 20
     shakeCooldown: 0
+
+  defaultParams =
+    duration: 10
+    intensity: 20

   self.bind "afterUpdate", ->
     I.shakeCooldown = I.shakeCooldown.approach(0, 1)
@@ -13,7 +17,11 @@

     return transform

-  shake: (duration = 10, intensity = 20) ->
+  shake: (options={}) ->
+    {duration, intensity} = Object.reverseMerge(options, defaultParams)
+
     I.shakeCooldown = duration * I.zoom
     I.shakeIntensity = intensity * I.zoom

+    self
+


Added auticomplete to address search on Gmaps Builder

--- app/assets/javascripts/gmaps-builder.js.coffee
+++ app/assets/javascripts/gmaps-builder.js.coffee
@@ -2,6 +2,7 @@

   @markerUser = "/images/marker-user.png"
   @markerPlace = "/images/marker-place.png"
+  @addressArray = []

   @newMap: (@latitude, @longitude, options) ->

@@ -64,6 +65,7 @@
             else
               Gmaps.validInputToGetLocation.valid()
               Gmaps.buttonToSearchAddress.enable()
+
               $(this).gmap3 marker:
                 latLng: results[0].geometry.location
                 options:
@@ -129,7 +131,7 @@
       address = this.value
       if address.length > 5
         Gmaps.validInputToGetLocation.searching()
-
+        Gmaps.autocompleteToSearchAddress.update(address)
         delay( ->
           Gmaps.getLocationFrom(address,
             draggable: true
@@ -227,9 +229,53 @@
       return

+
+  @autocompleteToSearchAddress:
+    active: ->
+      $('#address').autocomplete
+        source: Gmaps.addressArray
+        minLength: 3
+        delay: 500
+        appendTo: "#modal"
+
+      return
+
+    update: (address) ->
+      $("#address").gmap3
+        getlatlng:
+          address: address
+          callback: (results) ->
+
+            formatResults = (_results, callback) ->
+              if _results.length > 0
+                Gmaps.addressArray = []
+
+                _results = _results.slice(0,5)
+
+                _results.forEach((item) ->
+                  Gmaps.addressArray.push(item.formatted_address)
+                  return
+                )
+                callback()
+
+              return
+
+            formatResults(results, ->
+              $("#address").autocomplete "option", source: Gmaps.addressArray
+              return
+            )
+
+            console.log(Gmaps.addressArray)
+            return
+
+      return
+
+
+
   @setInitialAttributes: (options) ->
     Gmaps.activeSearch() if options.activeSearch is true
     Gmaps.setCanvasSize(options.canvasSize.width, options.canvasSize.height) if options.canvasSize isnt undefined
     Gmaps.buttonToSearchAddress.disable()
     Gmaps.showMapCanvasIfHidden()
+    Gmaps.autocompleteToSearchAddress.active()
     return


extract graph marker-creation into function.

--- app/assets/javascripts/plugins/graph.js.coffee
+++ app/assets/javascripts/plugins/graph.js.coffee
@@ -10,6 +10,7 @@
 colors = d3.scale.category10()
 edge_type = null # May be LogicMapping or Link
 mode = "normal"
+sentence_klasses = ["non_theorem", "proven", "unproven"]

 graphs_uri = window.location.href
 re = /\?[^/]+$/
@@ -48,6 +49,20 @@
 resetSelections = ->
   removeClass('g.node', 'selected')
   removeClass('path', 'selected')
+
+add_svg_marker = (svg_element, payload) ->
+  svg_element.append("svg:defs").selectAll("marker")
+    .data(payload)
+        .enter().append("svg:marker")
+          .attr("id", String)
+          .attr("viewBox", "0 -5 10 10")
+          .attr("refX", 14.5)
+          .attr("refY", -0.0)
+          .attr("markerWidth", 6)
+          .attr("markerHeight", 6)
+          .attr("orient", "auto")
+        .append("svg:path")
+          .attr("d", "M0,-6L10,0L0,6")

 jQuery ->
   $("div#d3_graph").html(HandlebarsTemplates['graphs/loading']({}))
@@ -124,19 +139,8 @@
     force.start()

     # Arrows
-    for klass in ["non_theorem", "proven", "unproven"]
-      svg_group.append("svg:defs").selectAll("marker")
-        .data([klass])
-        .enter().append("svg:marker")
-          .attr("id", String)
-          .attr("viewBox", "0 -5 10 10")
-          .attr("refX", 14.5)
-          .attr("refY", -0.0)
-          .attr("markerWidth", 6)
-          .attr("markerHeight", 6)
-          .attr("orient", "auto")
-        .append("svg:path")
-          .attr("d", "M0,-6L10,0L0,6")
+    add_svg_marker svg_group, [klass] for klass in sentence_klasses
+
     path = svg_group.append("svg:g").selectAll("path").
       data(force.links()).
       enter().


add username to query

add username to query

--- app/assets/javascripts/routers/users.js.coffee
+++ app/assets/javascripts/routers/users.js.coffee
@@ -7,6 +7,7 @@
     @collection = Likemarks.links

   show: (id) ->
+    $("#q").val("@#{id}")
     $("#main").html new Likemarks.Views.LinksIndex(
       collection: @collection.search(q: "@#{id}")
     ).render().el


When there are icons, reposition their tethers on load

--- app/js/jail_iframe/classes/icon_buttons.coffee
+++ app/js/jail_iframe/classes/icon_buttons.coffee
@@ -1,7 +1,19 @@
+there_are_icons = false
+
+FactlinkJailRoot.host_loaded_promise.then ->
+  FactlinkJailRoot.trigger 'reposition_icons'
+
+FactlinkJailRoot.on 'reposition_icons', ->
+  if there_are_icons
+    Tether.position()
+    console.log 'repositioned!'
+
 class IconButton
   constructor: (options) ->
     @_targetElement = options.targetElement
     @_targetOffset = options.targetOffset
+
+    there_are_icons = true
     @$el = $ """
       <factlink-icon-button>
         <factlink-icon-button-bubble>


Created a `normalize_caps_lock` option

Created a `normalize_caps_lock` option

Do not allow turning caps lock on to prevent combos from being
activated.

--- keypress.coffee
+++ keypress.coffee
@@ -21,19 +21,20 @@

 ###
 Combo options available and their defaults:
-    keys            : []            - An array of the keys pressed together to activate combo.
-    count           : 0             - The number of times a counting combo has been pressed. Reset on release.
-    is_unordered    : false         - Unless this is set to true, the keys can be pressed down in any order.
-    is_counting     : false         - Makes this a counting combo (see documentation).
-    is_exclusive    : false         - This combo will replace other exclusive combos when true.
-    is_solitary     : false         - This combo will only fire if ONLY it's keys are pressed down.
-    is_sequence     : false         - Rather than a key combo, this is an ordered key sequence.
-    prevent_default : false         - Prevent default behavior for all component key keypresses.
-    prevent_repeat  : false         - Prevent the combo from repeating when keydown is held.
-    on_keydown      : null          - A function that is called when the combo is pressed.
-    on_keyup        : null          - A function that is called when the combo is released.
-    on_release      : null          - A function that is called when all keys in the combo are released.
-    this            : undefined     - Defines the scope for your callback functions.
+    keys                  : []            - An array of the keys pressed together to activate combo.
+    count                 : 0             - The number of times a counting combo has been pressed. Reset on release.
+    is_unordered          : false         - Unless this is set to true, the keys can be pressed down in any order.
+    is_counting           : false         - Makes this a counting combo (see documentation).
+    is_exclusive          : false         - This combo will replace other exclusive combos when true.
+    is_solitary           : false         - This combo will only fire if ONLY it's keys are pressed down.
+    is_sequence           : false         - Rather than a key combo, this is an ordered key sequence.
+    prevent_default       : false         - Prevent default behavior for all component key keypresses.
+    prevent_repeat        : false         - Prevent the combo from repeating when keydown is held.
+    normalize_caps_lock   : false         - Do not allow turning caps lock on to prevent combos from being activated.
+    on_keydown            : null          - A function that is called when the combo is pressed.
+    on_keyup              : null          - A function that is called when the combo is released.
+    on_release            : null          - A function that is called when all keys in the combo are released.
+    this                  : undefined     - Defines the scope for your callback functions.
 ###

 ###########
@@ -41,12 +42,13 @@
 ###########

 _factory_defaults =
-    is_unordered    : false
-    is_counting     : false
-    is_exclusive    : false
-    is_solitary     : false
-    prevent_default : false
-    prevent_repeat  : false
+    is_unordered        : false
+    is_counting         : false
+    is_exclusive        : false
+    is_solitary         : false
+    prevent_default     : false
+    prevent_repeat      : false
+    normalize_caps_lock : false

 _modifier_keys = ["meta", "alt", "option", "ctrl", "shift", "cmd"]

@@ -359,7 +361,10 @@
     _match_combo_arrays: (potential_match, match_handler) ->
         # This will return all combos that match
         for source_combo in @_registered_combos
-            if (not source_combo.is_unordered and _compare_arrays_sorted(potential_match, source_combo.keys)) or (source_combo.is_unordered and _compare_arrays(potential_match, source_combo.keys))
+            combo_potential_match = potential_match.slice(0);
+            if (source_combo.normalize_caps_lock and "caps" in combo_potential_match)
+                combo_potential_match.splice(combo_potential_match.indexOf("caps"), 1)
+            if (not source_combo.is_unordered and _compare_arrays_sorted(combo_potential_match, source_combo.keys)) or (source_combo.is_unordered and _compare_arrays(combo_potential_match, source_combo.keys))
                 match_handler source_combo
         return


much better mouse interaction when using viewer

--- app/controllers/classifier.coffee
+++ app/controllers/classifier.coffee
@@ -27,7 +27,9 @@

   maxAnnotations: 5
   initialFetch: true
-  panKey: false
+  xDown: null
+  yDown: null
+  toAnnotate: true
   isTrainingSubject: false

   subjectGroup: '5154a3783ae74086ab000001'
@@ -98,6 +100,9 @@
     @hasWarned        = false
     @preset           = null
     @feedback         = false
+    @xDown            = null
+    @yDown            = null
+    @toAnnotate       = true

     @dashboardTutorial = false
     @isTrainingSubject = false
@@ -357,7 +362,7 @@
   #

   onAnnotation: (e) ->
-    return if @panKey
+    return unless @toAnnotate

     # Create annotation and push to object
     position = $('.subject.current .image').position()
@@ -449,7 +454,6 @@
   # Enabled only when viewer is ready
   wheelHandler: (e) =>
     e.preventDefault()
-    return if @panKey

     # Cache WebFITS object and push event
     wfits = @viewer.wfits
@@ -476,6 +480,9 @@
   setupMouseControls: (e) =>
     svg = @svg[0]

+    # Activate annotate flag
+    @toAnnotate = false
+
     # Update/reset Annotation class attributes
     Annotation.halfWidth = @viewer.wfits.width / 2
     Annotation.halfHeight = @viewer.wfits.height / 2
@@ -485,52 +492,69 @@
     # Setup mouse controls on the SVG element
     svg.addEventListener('mousewheel', @wheelHandler, false)

-    # Set up pan key and mouse events
-    $(document).keyup((e) => @panKey = false if e.keyCode is 32)
-    $(document).keydown((e) =>
-      if e.keyCode is 32
-        e.preventDefault()
-        @panKey = true
-    )
-
     # Pass events to viewer if pan key is true
     svg.onmousedown = (e) =>
-      @viewer.wfits.canvas.onmousedown(e) if @panKey
+
+      # Store the down position for later comparison
+      @xDown = e.pageX
+      @yDown = e.pageY
+
+      # TODO: Find more efficient way to do this
+      for key, a of @annotations
+        return if a.drag
+
+      @viewer.wfits.canvas.onmousedown(e)
+
     svg.onmouseup = (e) =>
-      @viewer.wfits.canvas.onmouseup(e) if @panKey
+
+      # Propagate to annotation layer if down coordinates match up coordinates
+      if @xDown is e.pageX and @yDown is e.pageY
+        @toAnnotate = true
+      else
+        @toAnnotate = false
+      return if @viewer.wfits.drag is false
+
+      @viewer.wfits.canvas.onmouseup(e)
+
     svg.onmousemove = (e) =>
-      if @panKey
-        # Pass event to WebFITS object
-        @viewer.wfits.canvas.onmousemove(e)
+      # Pass event to WebFITS object
+      @viewer.wfits.canvas.onmousemove(e)
+
+      # TODO: Find more efficient way to do this
+      for key, a of @annotations
+        return if a.drag
+
+      if @viewer.wfits.drag
+        # Update Annotation class attributes
+        Annotation.xOffset = xOffset = @viewer.wfits.xOffset
+        Annotation.yOffset = yOffset = @viewer.wfits.yOffset

-        if @viewer.wfits.drag
-          # Update Annotation class attributes
-          Annotation.xOffset = xOffset = @viewer.wfits.xOffset
-          Annotation.yOffset = yOffset = @viewer.wfits.yOffset
+        halfWidth = Annotation.halfWidth
+        halfHeight = Annotation.halfHeight
+        zoom = Annotation.zoom
+
+        # Translate origin
+        deltaX = halfWidth + xOffset
+        deltaY = halfHeight + yOffset
+
+        # Move element within pan-zoom reference frame
+        for key, a of @annotations
+          x = (a.x + deltaX - halfWidth) * zoom + halfWidth
+          y = (a.y - deltaY - halfHeight) * zoom + halfHeight

-          halfWidth = Annotation.halfWidth
-          halfHeight = Annotation.halfHeight
-          zoom = Annotation.zoom
-
-          # Translate origin
-          deltaX = halfWidth + xOffset
-          deltaY = halfHeight + yOffset
-
-          # Move element within pan-zoom reference frame
-          for key, a of @annotations
-            x = (a.x + deltaX - halfWidth) * zoom + halfWidth
-            y = (a.y - deltaY - halfHeight) * zoom + halfHeight
-
-            a.gRoot.setAttribute("transform", "translate(#{x}, #{y})")
+          a.gRoot.setAttribute("transform", "translate(#{x}, #{y})")

     svg.onmouseout = (e) =>
-      @viewer.wfits.canvas.onmouseout(e) if @panKey
+      @viewer.wfits.canvas.onmouseout(e)
     svg.onmouseover = (e) =>
-      @viewer.wfits.canvas.onmouseover(e) if @panKey
+      @viewer.wfits.canvas.onmouseover(e)

   onViewerClose: (e) =>
     @maskEl.removeClass('show')
     @viewerEl.removeClass('show')
+
+    # Allow annotation again
+    @toAnnotate = true

     # Reset Annotation attributes
     Annotation.xOffset = -220.5


insert logic for supplemental tutorial opt-in a/b splits

--- app/controllers/classifier.coffee
+++ app/controllers/classifier.coffee
@@ -161,6 +161,12 @@
     else
       console.log 'Setting mini-course interval to 5'
       @course.setRate 5 # set default
+
+    # HANDLE SUPPLEMENTAL TUTORIAL SPLITS
+    if @splitDesignation in ['a', 'b', 'c', 'g', 'h', 'i']
+      @tipsOptIn = true
+    else if @splitDesignation in ['d', 'e', 'f', 'j', 'k', 'l']
+      @tipsOptIn = false

     Subject.next() unless @classification?


added BitEUR to featured markets

added BitEUR to featured markets

--- app/js/controllers/markets.coffee
+++ app/js/controllers/markets.coffee
@@ -17,6 +17,7 @@
             $scope.featured_markets.push "BitCNY:#{main_asset.symbol}"
             $scope.featured_markets.push "BitBTC:#{main_asset.symbol}"
             $scope.featured_markets.push "BitGOLD:#{main_asset.symbol}"
+            $scope.featured_markets.push "BitEUR:#{main_asset.symbol}"

     $scope.select_market = (market) ->
         $scope.selected_market = market


Fixes #88

Fixes #88

--- @client/franklin.coffee
+++ @client/franklin.coffee
@@ -327,7 +327,7 @@
             points: buildPointsList \
               @proposal, 'cons', \
               (if mode == 'results' then 'score' else 'last_inclusion'), \
-              mode == 'crafting'
+              mode == 'crafting' && !TWO_COL()
             style:
               visibility: if !TWO_COL() && !has_community_points then 'hidden'

@@ -342,7 +342,7 @@
             points: buildPointsList \
               @proposal, 'pros', \
               (if mode == 'results' then 'score' else 'last_inclusion'), \
-              mode == 'crafting'
+              mode == 'crafting' && !TWO_COL()
             style:
               visibility: if !TWO_COL() && !has_community_points then 'hidden'

@@ -1231,9 +1231,16 @@

   points = (pnt for pnt in points when pnt.is_pro == (valence == 'pros') )

+  included_points = fetch(proposal.your_opinion).point_inclusions
   if filter_included
-    included_points = fetch(proposal.your_opinion).point_inclusions
     points = (pnt for pnt in points when !_.contains(included_points, pnt.key) )
+  else
+    for pnt in included_points
+      point = fetch pnt
+      continue if pnt.is_pro != (valence == 'pros')
+      if points.indexOf(point) == -1
+        points.push point
+

   # Filter down to the points included in the selection opinions, if set.
   hist = fetch(namespaced_key('histogram', proposal))
@@ -1259,9 +1266,7 @@
       if fetch(point).hide_name
         delete point_inclusions_per_point[point]

-
-  points = (pnt for pnt in points when pnt.key of point_inclusions_per_point)
-
+  points = (pnt for pnt in points when (pnt.key of point_inclusions_per_point) || (pnt.key in included_points))
   # Sort points based on resonance with selected users, or custom sort_field
   sort = (pnt) ->
     if filtered


explain the tabs/pages switcher at the bottom of each tab a bit

--- @client/homepage.coffee
+++ @client/homepage.coffee
@@ -127,6 +127,13 @@
                 DIV
                   style:
                     paddingTop: 28
+
+                  DIV
+                    style:
+                      textAlign: 'center'
+                      color: selected_color
+                      fontWeight: 600
+                    "Find more proposals on a different page:"

                   HomepageTabs
                     go_to_hash: 'active_tab'


Remove ability to hide sidebar by resizing

--- assets/javascripts/views/layout/resizer.coffee
+++ assets/javascripts/views/layout/resizer.coffee
@@ -14,7 +14,7 @@
     @appendTo $('._app')

     @style = $('style[data-resizer]')
-    @size = @savedSize = @style.getAttribute('data-size')
+    @size = @style.getAttribute('data-size')
     return

   MIN = 260
@@ -22,22 +22,13 @@

   resize: (value, save) ->
     value -= app.el.offsetLeft
-    return unless value >= 0
-
-    if value <= 5
-      app.document.hideSidebar({save})
-      newSize = @savedSize
-    else
-      app.document.showSidebar({save})
-      value = Math.min(Math.max(Math.round(value), MIN), MAX)
-      newSize = "#{value}px"
-
+    return unless value > 0
+    value = Math.min(Math.max(Math.round(value), MIN), MAX)
+    newSize = "#{value}px"
     @style.innerHTML = @style.innerHTML.replace(new RegExp(@size, 'g'), newSize)
     @size = newSize
-
     if save
-      @savedSize = @size
-      app.settings.setSize(parseInt(@savedSize))
+      app.settings.setSize(value)
       app.appCache?.updateInBackground()
     return

@@ -64,7 +55,7 @@
   onDragEnd: (event) =>
     $.off(window, 'dragover', @onDrag)
     value = event.pageX or (event.screenX - window.screenX)
-    if @lastDragValue and value > 0 and not (@lastDragValue - 5 < value < @lastDragValue + 5) # https://github.com/Thibaut/devdocs/issues/265
+    if @lastDragValue and not (@lastDragValue - 5 < value < @lastDragValue + 5) # https://github.com/Thibaut/devdocs/issues/265
       value = @lastDragValue
     @resize(value, true)
     return


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/clamp_bounds.coffee
+++ src/clamp_bounds.coffee
@@ -7,6 +7,7 @@

   App ||= {}
   App.width ||= 480
+  App.heigh

   self.bind "update", ->
     I.x = I.x.clamp(I.width / 2, App.width - I.width / 2)


ClampBounds docs

ClampBounds docs

--- src/clamp_bounds.coffee
+++ src/clamp_bounds.coffee
@@ -1,6 +1,6 @@
 ###*
 The ClampBounds module adds a check to make sure
-that the including game object
+that the including game object

 # Health regeneration


Remove retryNow after preset_loop fault

Remove retryNow after preset_loop fault

--- lib/cwmp.coffee
+++ lib/cwmp.coffee
@@ -338,6 +338,9 @@
           timestamp: sessionContext.timestamp
         }
         recordFault(sessionContext, fault)
+        # No need to save retryNow
+        for c of sessionContext.faults
+          delete sessionContext.faults[c].retryNow
         session.clearProvisions(sessionContext)
         return sendAcsRequest(sessionContext)


in progress on ticks.coffee

in progress on ticks.coffee

--- src/coffee/ticks.coffee
+++ src/coffee/ticks.coffee
@@ -372,8 +372,8 @@
     # so we subtract a small, index dependent amount from each difference
     # to force correct ordering.
     sh    = shape( differences )
-    small = 2.2e-16 * arange( sh[1] ) * arange( sh[0] )[:,newaxis]
-    small = small[::-1,::-1] #reverse the order
+    #small = 2.2e-16 * arange( sh[1] ) * arange( sh[0] )[:,newaxis]
+    #small = small[::-1,::-1] #reverse the order
     differences = differences - small

     # ? Numeric should allow keyword "axis" ? comment out for now
@@ -419,7 +419,7 @@
     if range == 0.0
         range = 1.0
     interval  = range / intervals
-    factor    = 10.0 ** Math.floor( log10( interval ) )
+    factor    = Math.exp(10, Math.floor( log10( interval ) ))
     interval = interval / factor

     if interval < 2.0
@@ -445,33 +445,34 @@
         interval  = interval *  [2.0, 1.25, 2.0, 2.0] [ index ]

-
-def log_auto_ticks(data_low, data_high,
+"""
+I'll worry about this after getting linear ticks working
+log_auto_ticks = (data_low, data_high,
                    bound_low, bound_high,
-                   tick_interval, use_endpoints = true):
-    """Like auto_ticks(), but for log scales."""
+                   tick_interval, use_endpoints=true) ->
+    #Like auto_ticks(), but for log scales.
     tick_goal = 15
     magic_numbers = [1, 2, 5]
     explicit_ticks = false

-    if data_low<=0.0:
+    if data_low <= 0.0
         return []

-    if tick_interval != 'auto':
-        if tick_interval < 0:
+    if tick_interval != 'auto'
+        if tick_interval < 0
             tick_goal = -tick_interval
-        else:
+        else
             magic_numbers = [tick_interval]
             explicit_ticks = true

-    if data_low>data_high:
-        data_low, data_high = data_high, data_low
+    if data_low>data_high
+        [data_low, data_high] = data_high, data_low

     log_low = log10(data_low)
     log_high = log10(data_high)
     log_interval = log_high-log_low

-    if log_interval < 1.0:
+    if log_interval < 1.0
         # If less than a factor of 10 separates the data, just use the normal
         # linear approach
         return auto_ticks(data_low, data_high,
@@ -479,17 +480,17 @@
                           tick_interval,
                           use_endpoints = false)

-    else if log_interval < (tick_goal+1)/2 or explicit_ticks:
+    else if log_interval < (tick_goal+1)/2 or explicit_ticks
         # If there's enough space, try to put lines at the magic number multipliers
         # inside each power of ten

         # Try each interval to see how many ticks we get
-        for interval in magic_numbers:
+        for interval in magic_numbers
             ticklist = []
-            for exp in range(int(Math.floor(log_low)), int(Math.ceil(log_high))):
+            for exp in [Math.floor(log_low).,Math.ceil(log_high)]
                 for multiplier in linspace(interval, 10.0, round(10.0/interval),
-                                           endpoint=1):
-                    tick = 10**exp*multiplier
+                                           endpoint=1)
+                    tick = Math.exp(10, exp*multiplier)
                     if tick >= data_low and tick <= data_high:
                         ticklist.append(tick)
             if len(ticklist)<tick_goal+3 or explicit_ticks:
@@ -505,27 +506,27 @@
         if (endlog-startlog) % interval == 0.0:
             expticks = concatenate([expticks, [endlog]])
         return 10**expticks
-
+"""

 #-------------------------------------------------------------------------------
 #  Compute the best lower and upper axis bounds for a range of data:
 #-------------------------------------------------------------------------------

-def auto_bounds ( data_low, data_high, tick_interval ):
+auto_bounds = ( data_low, data_high, tick_interval ) ->
     """ Calculates appropriate upper and lower bounds for the axis from
         the data bounds and the given axis interval.

         The boundaries  hit either exactly on the lower and upper values
         or on the tick mark just beyond the lower and upper values.
     """
-    return ( calc_bound( data_low,  tick_interval, false ),
-             calc_bound( data_high, tick_interval, true  ) )
+    return [calc_bound( data_low,  tick_interval, false ),
+             calc_bound( data_high, tick_interval, true  ) ]

 #-------------------------------------------------------------------------------
 #  Compute the best axis endpoint for a specified data value:
 #-------------------------------------------------------------------------------

-def calc_bound ( end_point, tick_interval, is_upper ):
+calc_bound =  ( end_point, tick_interval, is_upper ) ->
     """ Finds an axis end point that includes the value *end_point*.

     If the tick mark interval results in a tick mark hitting directly on the
@@ -534,13 +535,15 @@
     specifies whether *end_point* is at the upper (True) or lower (false)
     end of the axis.
     """
-    quotient, remainder = divmod( end_point, tick_interval )
-    if ((remainder == 0.0) or
-        (((tick_interval - remainder) / tick_interval) < 0.00001)):
+    [quotient, remainder] = divmod( end_point, tick_interval )
+    rem_p = remainder == 0.0
+    tick_p = ((tick_interval - remainder) / tick_interval) < 0.00001
+    if rem_p or tick_p
         return end_point

     c1 = (quotient + 1.0) * tick_interval
     c2 = quotient         * tick_interval
-    if is_upper:
+    if is_upper
         return max( c1, c2 )
     return min( c1, c2 )
+


activated experimental link trimming

activated experimental link trimming

--- app/coffee/main.coffee
+++ app/coffee/main.coffee
@@ -44,17 +44,15 @@
   initialize_force_layout = ->
     force = d3.layout.force()
       .size [width, height]
-      .linkDistance 50
-        # base     = 150
-        # grouping = if @group_by(d.source) == @group_by(d.target) then 40 else 0
-        # weight   = @link_weight(d) * 10
-        # base - grouping - weight
+      .linkDistance (d) ->
+        base     = 150
+        grouping = if group_by(d.source) == group_by(d.target) then 40 else 0
+        weight   = link_weight(d) * 10
+        base - grouping - weight
       .linkStrength (d) ->
         if group_by(d.source) == group_by(d.target) then 0.05 else .01
-      .charge (d) ->
-        # -9 * @node_size(d)
-        -200
-      .gravity 0.03
+      .charge -200
+      .gravity 0.3

   # https://github.com/Caged/d3-tip/blob/master/docs/index.md#d3tip-api-documetation
   node_tip = undefined
@@ -488,7 +486,8 @@
         else
           null

-    _.compact _.flatten(links)
+    links = _.compact _.flatten(links)
+    _.sortBy( links, (link) -> -link_weight(link) )[0..500]

   similarity_exclusions = ->
     $.makeArray( ScholarMapViz.$similarity_types.find('input[type="checkbox"]:not(:checked)') ).map (type) ->


:art: Auto-Dismiss notifications after 5 seconds

--- lib/hack.coffee
+++ lib/hack.coffee
@@ -5,9 +5,15 @@
   config: type:'local', status:true
   SSH: null
   showError:(Message)->
-    atom.notifications.addError("[Hack] #{Message}", {dismissable: true})
+    Notification = atom.notifications.addError("[Hack] #{Message}", {dismissable: true})
+    setTimeout ->
+      Notification.dismiss()
+    , 5000
   showSuccess:(Message)->
-    atom.notifications.addSuccess("[Hack] #{Message}", {dismissable: true})
+    Notification = atom.notifications.addSuccess("[Hack] #{Message}", {dismissable: true})
+    setTimeout ->
+      Notification.dismiss()
+    , 5000
   exec:(Command, CWD)->
     return new Promise (Resolve, Reject)=>
       if @config.type is 'remote'


Fix a bug in the warning regex

Fix a bug in the warning regex

For some mix output the old version was grabbing two lines of text as the "file name".

--- lib/init.coffee
+++ lib/init.coffee
@@ -109,7 +109,7 @@
     parseWarning = (toParse, textEditor) ->
       ret = []
       re = ///
-        ([^:]*) # 1 - File name
+        ([^:\n]*) # 1 - File name
         :(\d+)  # 2 - Line
         :\ warning
         :\ (.*) # 3 - Message
@@ -145,8 +145,9 @@
       env = if isTestFile(textEditor) then "test" else "dev"
       buildDir = path.join("_build", env, "lib")
       try
-        fs.readdirSync(path.join(projectPath(textEditor), buildDir)).map (item) ->
-          path.join(projectPath(textEditor), buildDir, item, "ebin")
+        fs.readdirSync(path.join(projectPath(textEditor), buildDir)).
+          map (item) ->
+            path.join(projectPath(textEditor), buildDir, item, "ebin")
       catch e
         []


tweaks and fixes

tweaks and fixes

--- lib/main.coffee
+++ lib/main.coffee
@@ -93,7 +93,7 @@
       @identifyCore(event.port)

     @emitter.on "#{@packageName()}:compile-cloud", (event) =>
-      @compileCloud(event.thenFlash)
+      @compileCloud(event.thenFlash, event.files, event.rootPath)

     @emitter.on "#{@packageName()}:flash-cloud", (event) =>
       @flashCloud(event.firmware)
@@ -141,8 +141,10 @@

     contextMenus = {
       '.tree-view.full-menu [is="tree-view-file"] [data-name$=".cpp"]':flashExampleMenuItem,
-      '.tree-view.full-menu [is="tree-view-file"] [data-name$=".ino"]':flashExampleMenuItem
-      '.tree-view.full-menu [is="tree-view-directory"]':flashExampleMenuItem
+      '.tree-view.full-menu [is="tree-view-file"] [data-name$=".ino"]':flashExampleMenuItem,
+# when matching the directory, the item is also propagated to all child elements of that directory regardless
+# of their extension, so for now compiling an example is done only from the files themselves
+#      '.tree-view.full-menu [is="tree-view-directory"]':flashExampleMenuItem
     }

     @contextMenus.add atom.contextMenu.add contextMenus
@@ -556,11 +558,12 @@
     relative = []
     libraryManager.pathsCommonPrefix files, relative, basePath
     map = {}
-    for i in [0..files.length]
+    for i in [0..files.length-1]
       map[relative[i]] = files[i]
     return map

   # Compile current project in the cloud
+  # when updating arguments here, also update the event emitter above
   compileCloud: (thenFlash=null, files=null, rootPath=null) -> @loginRequired =>
     if !@canCompileNow
       return
@@ -643,10 +646,8 @@
           @SettingsHelper.setLocal 'compile-status', {filename: filename}
           atom.commands.dispatch @workspaceElement, "#{@packageName()}:update-compile-status"
           if !!thenFlash
-            setTimeout =>
-              atom.commands.dispatch @workspaceElement, "#{@packageName()}:flash-cloud"
-              @downloadBinaryPromise = null
-            , 500
+            # want to explicitly set the file to flash since we cannot assume it from the current project when compiling a library example
+            @emitter.emit "#{@packageName()}:flash-cloud", {firmware: filename}
           else
             @downloadBinaryPromise = null
         , (e) =>
@@ -683,31 +684,37 @@
     @compileErrorsView.show()

   # Flash core via the cloud
-  flashCloud: (firmware=null) -> @deviceRequired => @projectRequired =>
+  flashCloud: (firmware=null) -> @deviceRequired =>
     fs ?= require 'fs-plus'
     path ?= require 'path'
     _s ?= require 'underscore.string'
     utilities ?= require './vendor/utilities'
+    console.log 'flashCloud', firmware

     currentPlatform = @getCurrentPlatform()
-    rootPath = @getProjectDir()
-    files = fs.listSync(rootPath)
-    files = files.filter (file) ->
-      return (utilities.getFilenameExt(file).toLowerCase() == '.bin') &&
-             (_s.startsWith(path.basename(file), currentPlatform))
-
-    if files.length == 0 && (!firmware)
+
+    files = null
+    rootPath = null
+    if firmware
+      files = [firmware]
+    else
+      @projectRequired =>
+        rootPath = @getProjectDir()
+        files = fs.listSync(rootPath)
+        files = files.filter (file) ->
+          return (utilities.getFilenameExt(file).toLowerCase() == '.bin') &&
+                 (_s.startsWith(path.basename(file), currentPlatform))
+
+    if files.length is 0
       # If no firmware file, compile
       @emitter.emit "#{@packageName()}:compile-cloud", {thenFlash: true}
-    else if (files.length == 1) || (!!firmware)
+    else if (files.length == 1)
       # If one firmware file, flash
-
-      if !firmware
-        firmware = files[0]
-
+      firmware = files[0]
       flashDevice = =>
-        process.chdir rootPath
-        firmware = path.relative rootPath, firmware
+        if !!rootPath
+          process.chdir rootPath
+          firmware = path.relative rootPath, firmware

         @statusView.setStatus 'Flashing via the cloud...'

@@ -791,14 +798,15 @@
           example.buildFiles(files)
             .then =>
               console.log('compiling example files', files)
-              @compileCloud true, files.map, files.basePath
-
-  flashCloudFile: (event) ->
+              @emitter.emit "#{@packageName()}:compile-cloud", {thenFlash: true, files: files.map, rootPath: files.basePath}
+              #@compileCloud true, files.map, files.basePath
+
+  flashCloudFile: (event) -> @deviceRequired =>
     @flashCloud event.target.dataset.path

-  flashCloudExampleFile: (event) ->
+  flashCloudExampleFile: (event) -> @deviceRequired =>
     file = event.target.dataset.path
-    @flashCloudExample file
+    @emitter.emit "#{@packageName()}:flash-cloud-example", {file}

   # Show serial monitor panel
   showSerialMonitor: ->


main: Keep watch sidebar in sync with editor

--- lib/main.coffee
+++ lib/main.coffee
@@ -25,6 +25,9 @@

     statusBarElement: null
     statusBarTile: null
+
+    watchSidebar: null
+    watchSidebarIsVisible: false

     activate: (state) ->
         @kernelManager = new KernelManager()
@@ -51,8 +54,14 @@
             'hydrogen:toggle-watches': => @toggleWatchSidebar()
             'hydrogen:select-watch-kernel': => @showWatchKernelPicker()
             'hydrogen:select-kernel': => @showKernelPicker()
-            'hydrogen:add-watch': => @watchSidebar.addWatchFromEditor()
-            'hydrogen:remove-watch': => @watchSidebar.removeWatch()
+            'hydrogen:add-watch': =>
+                unless @watchSidebarIsVisible
+                    @toggleWatchSidebar()
+                @watchSidebar?.addWatchFromEditor()
+            'hydrogen:remove-watch': =>
+                unless @watchSidebarIsVisible
+                    @toggleWatchSidebar()
+                @watchSidebar?.removeWatch()
             'hydrogen:update-kernels': => @kernelManager.updateKernelSpecs()
             'hydrogen:toggle-inspector': => @inspector.toggle()
             'hydrogen:interrupt-kernel': =>
@@ -96,6 +105,7 @@

     onKernelChanged: (@kernel) ->
         @setStatusBar()
+        @setWatchSidebar @kernel

     setStatusBar: ->
@@ -116,6 +126,33 @@

         while @statusBarElement.hasChildNodes()
             @statusBarElement.removeChild @statusBarElement.lastChild
+
+
+    setWatchSidebar: (kernel) ->
+        console.log 'setWatchSidebar:', kernel
+
+        sidebar = kernel?.watchSidebar
+        if @watchSidebar is sidebar
+            return
+
+        if @watchSidebar?.visible
+            @watchSidebar.hide()
+
+        @watchSidebar = sidebar
+
+        if @watchSidebarIsVisible
+            @watchSidebar?.show()
+
+
+    toggleWatchSidebar: ->
+        if @watchSidebarIsVisible
+            console.log 'toggleWatchSidebar: hiding sidebar'
+            @watchSidebarIsVisible = false
+            @watchSidebar?.hide()
+        else
+            console.log 'toggleWatchSidebar: showing sidebar'
+            @watchSidebarIsVisible = true
+            @watchSidebar?.show()

     showKernelCommands: ->
@@ -154,7 +191,6 @@
             @kernelManager.startKernel kernelSpec, grammar, =>
                 kernel = @kernelManager.getRunningKernelFor language
                 @onKernelChanged kernel
-                @setWatchSidebar kernel

         else if command is 'switch-kernel'
             if kernel
@@ -163,7 +199,6 @@
             @kernelManager.startKernel kernelSpec, grammar, =>
                 kernel = @kernelManager.getRunningKernelFor language
                 @onKernelChanged kernel
-                @setWatchSidebar kernel

     createResultBubble: (code, row) ->
@@ -177,9 +212,7 @@

     _createResultBubble: (kernel, code, row) ->
-        unless @watchSidebar?
-            @setWatchSidebar kernel
-        else if @watchSidebar.element.contains document.activeElement
+        if @watchSidebar.element.contains document.activeElement
             @watchSidebar.run()
             return

@@ -304,38 +337,6 @@
                 endRow -= 1
         return endRow

-    hideWatchSidebar: ->
-        unless @watchSidebar?
-            console.log 'hideWatchSidebar: there is no sidebar'
-            return
-
-        @watchSidebar.hide()
-
-    showWatchSidebar: ->
-        unless @watchSidebar?
-            console.log 'showWatchSidebar: there is no sidebar'
-            return
-
-        @watchSidebar.show()
-
-    toggleWatchSidebar: ->
-        if @watchSidebar?.visible
-            console.log 'toggleWatchSidebar: hiding sidebar'
-            @watchSidebar.hide()
-        else
-            console.log 'toggleWatchSidebar: showing sidebar'
-            @watchSidebar.show()
-
-    setWatchSidebar: (kernel) ->
-        sidebar = kernel.watchSidebar
-        console.log 'setting watch sidebar'
-        if @watchSidebar isnt sidebar and @watchSidebar?.visible
-            @watchSidebar.hide()
-            @watchSidebar = sidebar
-            @watchSidebar.show()
-        else
-            @watchSidebar = sidebar
-
     showKernelPicker: ->
         unless @kernelPicker?
             @kernelPicker = new KernelPicker (callback) =>
@@ -349,8 +350,10 @@
                     kernelSpec: kernelSpec
         @kernelPicker.toggle()

-
     showWatchKernelPicker: ->
+        unless @watchSidebarIsVisible
+            @toggleWatchSidebar()
+
         unless @watchKernelPicker?
             @watchKernelPicker = new KernelPicker (callback) =>
                 kernels = @kernelManager.getAllRunningKernels()
@@ -363,7 +366,6 @@
                 kernel = kernels[0]
                 if kernel
                     @setWatchSidebar kernel
-                    @watchSidebar.show()
         @watchKernelPicker.toggle()

     findCodeBlock: ->


WebGL parameter change

WebGL parameter change

--- lib/main.coffee
+++ lib/main.coffee
@@ -361,9 +361,9 @@
     gravity = if (param['gravity']?) then param['gravity'] else 0.0
     image = if (param['image']?) then param['image'] else undefined
     model = if (param['model']?) then param['model'] else undefined
-    width = if (param['width']?) then param['width'] else SCREEN_WIDTH
-    height = if (param['height']?) then param['height'] else SCREEN_HEIGHT
-    depth = if (param['depth']?) then param['depth'] else 100.0
+    width = if (param['width']?) then param['width'] else 1
+    height = if (param['height']?) then param['height'] else 1
+    depth = if (param['depth']?) then param['depth'] else 1.0
     opacity = if (param['opacity']?) then param['opacity'] else 1.0
     animlist = if (param['animlist']?) then param['animlist'] else undefined
     animnum = if (param['animnum']?) then param['animnum'] else 0
@@ -373,9 +373,9 @@
     density = if (param['density']?) then param['density'] else 1.0
     friction = if (param['friction']?) then param['friction'] else 1.0
     restitution = if (param['restitution']?) then param['restitution'] else 1.0
-    radius = if (param['radius']?) then param['radius'] else 0.0
-    radius2 = if (param['radius2']?) then param['radius2'] else 0.0
-    size = if (param['size']?) then param['size'] else 100.0
+    radius = if (param['radius']?) then param['radius'] else 1.0
+    radius2 = if (param['radius2']?) then param['radius2'] else 1.0
+    size = if (param['size']?) then param['size'] else 1.0
     scaleX = if (param['scaleX']?) then param['scaleX'] else 1.0
     scaleY = if (param['scaleY']?) then param['scaleY'] else 1.0
     scaleZ = if (param['scaleZ']?) then param['scaleZ'] else 1.0


Use Workspace::addOpener

Use Workspace::addOpener

--- lib/main.coffee
+++ lib/main.coffee
@@ -53,7 +53,7 @@
     atom.commands.add '.tree-view .file .name[data-name$=\\.ron]', 'markdown-preview:preview-file', previewFile
     atom.commands.add '.tree-view .file .name[data-name$=\\.text]', 'markdown-preview:preview-file', previewFile

-    atom.workspace.registerOpener (uriToOpen) ->
+    atom.workspace.addOpener (uriToOpen) ->
       try
         {protocol, host, pathname} = url.parse(uriToOpen)
       catch error