JDomain: Add fetchDomains, tidy up the code

--- workers/social/lib/social/models/domain.coffee
+++ workers/social/lib/social/models/domain.coffee
@@ -39,6 +39,8 @@
           (signature String, Function)
         registerDomain:
           (signature Object, Function)
+        fetchDomains:
+          (signature Function)
         getTldList:
           (signature Function)
         createDomain: [
@@ -156,6 +158,44 @@
         default     : -> new Date
       stack         : ObjectId

+  # filters domains such as shared-x/vm-x.groupSlug.kd.io
+  # or x.koding.kd.io. Also shows only group related
+  # domains to users
+  filterDomains = (domains, account, group)->
+    domainList = []
+    domainList = domains.filter (domain)->
+      {domain} = domain
+      re = if group is "koding" then new RegExp(account.profile.nickname + '\.kd\.io$') \
+           else new RegExp('(.*)\.' + group + '\.kd\.io$')
+      isVmAlias         = (/^shared|vm[\-]?([0-9]+)?/.test domain)
+      isKodingSubdomain = (/(.*)\.(koding|guests)\.kd\.io$/.test domain)
+      isGroupAlias      = re.test domain
+      not isVmAlias and not isKodingSubdomain and isGroupAlias
+
+  @fetchDomains: secure (client, callback)->
+
+    {group} = client.context
+    {connection: {delegate}} = client
+
+    JGroup.one {slug:group}, (err, group)->
+
+      return callback err  if err
+      return callback new KodingError("No group found.")  unless group
+
+      delegate.checkPermission group, 'list own domains', (err, hasPermission)->
+
+        return callback err  if err
+        unless hasPermission
+          return callback new KodingError "Access denied",  "ACCESSDENIED"
+
+        delegate.fetchDomains (err, domains) ->
+          return callback err  if err
+
+          domainList = []
+          domainList = filterDomains domains, delegate, group.slug  if domains
+
+          callback null, domainList
+
   @isDomainEligible: (params, callback)->
     {domain, newDomain} = params

@@ -172,6 +212,7 @@
     callback null, slug

   @createDomain: secure (client, options, callback)->
+
     [callback, options] = [options, callback]  unless callback
     {delegate} = client.connection
     {domain} = options
@@ -210,6 +251,49 @@
                 return callback err if err

               callback err, model
+
+  bindVM: (client, params, callback)->
+    domainName = @domain
+    operation  = {'$addToSet': hostnameAlias: params.hostnameAlias}
+    JDomain.update {domain:domainName}, operation, callback
+
+  unbindVM: (client, params, callback)->
+    domainName = @domain
+    operation  = {'$pull': hostnameAlias: params.hostnameAlias}
+    JDomain.update {domain:domainName}, operation, callback
+
+  bindVM$: permit
+    advanced: [
+      { permission: "edit own domains", validateWith: Validators.own }
+    ]
+    success: (rest...)-> @bindVM rest...
+
+  unbindVM$: permit
+    advanced: [
+      { permission: "edit own domains", validateWith: Validators.own }
+    ]
+    success: (rest...)-> @unbindVM rest...
+
+  @one$: permit 'list domains',
+    success: (client, selector, callback)->
+      {delegate} = client.connection
+      delegate.fetchDomains (err, domains)->
+        return callback err if err
+        for domain in domains
+          # console.log "Testing domain:", domain, selector.domainName
+          return callback null, domain if domain.domain is selector.domainName
+
+  remove$: permit
+    advanced: [
+      { permission: 'delete own domains', validateWith: Validators.own }
+    ]
+    success: (client, callback)->
+      {delegate} = client.connection
+      if /^([\w\-]+)\.kd\.io$/.test @domain
+        return callback message: "It's not allowed to delete root domains"
+      @remove (err)=> callback err
+
+  # DOMAIN REGISTER STUFF ~ WIP

   @getTldList = (callback)->
     domainManager.domainService.getAvailableTlds callback
@@ -292,47 +376,6 @@
     JPaymentCharge.charge client, data, callback

   bound: require 'koding-bound'
-
-  bindVM: (client, params, callback)->
-    domainName = @domain
-    operation  = {'$addToSet': hostnameAlias: params.hostnameAlias}
-    JDomain.update {domain:domainName}, operation, callback
-
-  unbindVM: (client, params, callback)->
-    domainName = @domain
-    operation  = {'$pull': hostnameAlias: params.hostnameAlias}
-    JDomain.update {domain:domainName}, operation, callback
-
-  bindVM$: permit
-    advanced: [
-      { permission: "edit own domains", validateWith: Validators.own }
-    ]
-    success: (rest...)-> @bindVM rest...
-
-  unbindVM$: permit
-    advanced: [
-      { permission: "edit own domains", validateWith: Validators.own }
-    ]
-    success: (rest...)-> @unbindVM rest...
-
-  @one$: permit 'list domains',
-    success: (client, selector, callback)->
-      {delegate} = client.connection
-      delegate.fetchDomains (err, domains)->
-        return callback err if err
-        for domain in domains
-          # console.log "Testing domain:", domain, selector.domainName
-          return callback null, domain if domain.domain is selector.domainName
-
-  remove$: permit
-    advanced: [
-      { permission: 'delete own domains', validateWith: Validators.own }
-    ]
-    success: (client, callback)->
-      {delegate} = client.connection
-      if /^([\w\-]+)\.kd\.io$/.test @domain
-        return callback message: "It's not allowed to delete root domains"
-      @remove (err)=> callback err

   # DNS Related Methods

@@ -469,10 +512,3 @@
     success: (client, params, callback)->
       params.domainName = @domain
       JProxyRestriction.deleteRule params, (err)-> callback err
-
-  @fetchGroupDomains: secure (client, callback)->
-    JVM = require './vm'
-    JVM.fetchVmsByContext client, {}, (err, vms) ->
-      return callback err if err
-      JDomain.some hostnameAlias : $in : vms, {}, callback
-


fixed issue #23 and fixed problem when file had no titles

--- src/manifest.coffee
+++ src/manifest.coffee
@@ -68,7 +68,7 @@
         @sections = []
         @html = ''

-        convertToHtml = (markdown) ->
+        convertToHtml = (markdown) =>
             html = marked markdown
             imgs = html.match /]*>/gi
             for img in imgs || []
@@ -82,10 +82,12 @@
                         html = html.replace img, new_img

             hTags = html.match /<h([1-6])>.+<\/h\1>/gi
-            for hTag in hTags
+            for hTag in hTags || []
                 title = hTag.substring hTag.indexOf('>') + 1, hTag.lastIndexOf('<')
                 anchor = S(title).stripTags().decodeHTMLEntities().slugify().s
                 html = html.replace hTag, '' + hTag
+
+            return html

         titles = @raw.match /^\#+ (.+)$/gm
         for i, mdTitle of titles
@@ -101,6 +103,12 @@
             html = convertToHtml content
             @html += html
             @sections.push {slug: slug, level: level, title: title, markdown: content, html: html}
+
+        if @sections.length == 0
+            title = S(extractNameFromUri(@uri)).humanize().s
+            slug = S(title).slugify().s
+            @html = convertToHtml @raw
+            @sections.push {slug: slug, level: 1, title: title, markdown: @raw, html: @html}

 # Represents a manifest


fix bug in file +New with no extension

fix bug in file +New with no extension

--- salvus/page/project_new.cjsx
+++ salvus/page/project_new.cjsx
@@ -130,7 +130,7 @@
         </MenuItem>

     render : ->
-        <SplitButton title={@file_dropdown_icon()} onClick={=>@create_file()}>
+        <SplitButton title={@file_dropdown_icon()} onClick={=>@props.create_file()}>
             {(@file_dropdown_item(i, ext) for i, ext of new_file_button_types)}
         </SplitButton>


atom: tablengths for latex

atom: tablengths for latex

(cherry picked from commit 3d24a78d7d576071ab39846a725ce9321a54df4a)

--- .atom/config.cson
+++ .atom/config.cson
@@ -57,3 +57,9 @@
 ".python.source":
   whitespace:
     removeTrailingWhitespace: true
+".tex.text":
+  editor:
+    tabLength: 2
+"text.latex.tex":
+  editor:
+    tabLength: 2


fix session

fix session

--- src/server/lib/session.coffee
+++ src/server/lib/session.coffee
@@ -37,9 +37,9 @@
     User.connect (client) =>
       User.create_session client, user_id, (session_data) =>
         if session_data?.ses_id
-          req.session.session_id = session_data.ses_id
-          req.session.user_id = user_id
-          req.session.metadata = metadata if metadata
+          req.session?.session_id = session_data.ses_id
+          req.session?.user_id = user_id
+          req.session?.metadata = metadata if metadata
           key = "app:session:#{crypto.createHash('md5').update(session_data.ses_id).digest('hex')}"
           ttl = @config.session_ttl
           value = 'true'


include figure plugin

include figure plugin

--- scripts/configs/aloha.coffee
+++ scripts/configs/aloha.coffee
@@ -67,6 +67,7 @@
         'oer/multipart'
         'oer/copy'
         'oer/cleanup'
+        'oer/figure'
         'oer/media-embed'
         'oer/metadata'
       ]


webapp: stupid fix for firefox, otherwise content doesn' show up

--- src/smc-webapp/jupyter.coffee
+++ src/smc-webapp/jupyter.coffee
@@ -68,6 +68,7 @@
     constructor: (@editor, @filename, @content, opts) ->
         @element = templates.find(".smc-jupyter-nbviewer").clone()
         @ipynb_filename = @filename.slice(0,@filename.length-4) + 'ipynb'
+        @ipynb_html_src = "/#{@editor.project_id}/raw/#{@filename}"
         @init_buttons()

     show: () =>
@@ -76,8 +77,13 @@
             # We do this, since otherwise just loading the iframe using
             #      @iframe.contents().find('html').html(@content)
             # messes up the parent html page, e.g., foo.modal() is gone.
-            @iframe.contents().find('body')[0].innerHTML = @content
-            @iframe.contents().find("body").on("click mousemove keydown focusin", smc.client.reset_idle)
+            # setting the content this way, works in Chrome, but not FF
+            #@iframe.contents().find('body').first().html(@content)
+            # FIXME although really bad overhead, this is a quick fix for FF
+            # callback, run after "load" event below this line
+            @iframe.load ->
+                @iframe.contents().find("body").on("click mousemove keydown focusin", smc.client.reset_idle)
+            @iframe.attr('src', @ipynb_html_src)

         @element.css(top:@editor.editor_top_position())
         @element.maxheight(offset:18)
@@ -104,7 +110,7 @@
         open = @element.find('a[href=#open]')
         open.css("margin-right", "10px")
         open.attr("target", "_blank")
-        open.attr("href", "/#{@editor.project_id}/raw/#{@filename}")
+        open.attr("href", @ipynb_html_src)

 ipython_notebook_server = (opts) ->
     console.log("ipython_notebook_server")


don't update primus in kucalc

don't update primus in kucalc

--- src/smc-hub/hub.coffee
+++ src/smc-hub/hub.coffee
@@ -601,7 +601,7 @@
                         host     : program.host
                         cb       : cb
                 (cb) ->
-                    if program.dev or program.update
+                    if (program.dev or program.update) and not program.kucalc
                         update_primus(cb)
                     else
                         cb()


Standardize on 0-indexed in main description

All of the Reading Text functions have `0-indexed` in their main
description except this one, which had moved it into the description of
the argument. Standardize them all on the same style.

--- src/text-buffer.coffee
+++ src/text-buffer.coffee
@@ -667,9 +667,10 @@
   getLastLine: ->
     @lineForRow(@getLastRow())

-  # Public: Get the text of the line at the given row, without its line ending.
-  #
-  # * `row` A {Number} representing a 0-indexed row.
+  # Public: Get the text of the line at the given 0-indexed row, without its
+  # line ending.
+  #
+  # * `row` A {Number} representing the row.
   #
   # Returns a {String}.
   lineForRow: (row) -> @buffer.lineForRow(row)


Add getMarkerLayer method to TextBuffer

Add getMarkerLayer method to TextBuffer

--- src/text-buffer.coffee
+++ src/text-buffer.coffee
@@ -788,6 +788,12 @@
     layer = new MarkerStore(this, String(@nextMarkerLayerId++))
     @customMarkerLayers.push(layer)
     layer
+
+  getMarkerLayer: (id) ->
+    for customMarkerLayer in @customMarkerLayers
+      if customMarkerLayer.id is id
+        return customMarkerLayer
+    undefined

   # Public: Create a marker with the given range. This marker will maintain
   # its logical location as the buffer is changed, so if you mark a particular


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/text_effect.coffee
+++ src/text_effect.coffee
@@ -1,7 +1,7 @@
 ###*
 The Text Effect class provides a method to display moving text onscreen, fading out the text over the effect duration.

-    Tex
+    TextEffect

 @name TextEffect
 @constructor


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/text_screen.coffee
+++ src/text_screen.coffee
@@ -25,7 +25,8 @@

     @name centerText
     @methodOf TextScreen#
-    @param {PixieCanvas} canvas The canvas to draw
+    @param {PixieCanvas} canvas The canvas to draw on
+
     ###
     centerText: (canvas, text, options={}) ->
       font = options.font || I.font


add tests on forget routes

add tests on forget routes

--- test/server/routes/api/users.coffee
+++ test/server/routes/api/users.coffee
@@ -274,4 +274,20 @@
             .end (e, res) ->
               User.findById sampleUser.id, (err, dbUser) ->
                 expect(dbUser).to.not.exist
-                done()+                done()
+
+  describe 'POST /forget', ->
+    it 'returns a 404 if user is not exist', (done) ->
+      request app
+        .post "/#{config.apiSegment}/forget"
+        .send
+          email: 'random@example.com'
+        .expect 404
+        .end done
+
+    it 'returns a 404 if payload is not json', (done) ->
+      request app
+        .post "/#{config.apiSegment}/forget"
+        .send 'Invalid JSON payload'
+        .expect 404
+        .end done


disable debug for zombie instances

disable debug for zombie instances

--- server/test/integration/indicator_admin.coffee
+++ server/test/integration/indicator_admin.coffee
@@ -21,7 +21,7 @@
     Promise.promisify(Indicator.create, Indicator)(importedIndicator)
   )

-  browser = new Browser(debug: true)
+  browser = new Browser()

   spreadsheetKey = '43289432'


change library impl's name, format stats line with color

--- benchmark/index.coffee
+++ benchmark/index.coffee
@@ -1,4 +1,10 @@
 benchmarked = require 'benchmarked'
+format = require 'comma-number'
+pad = require 'pad'
+chalk = require 'chalk'
+bold  = chalk.bold
+dim   = chalk.dim
+grey  = chalk.grey

 ask = require('readline').createInterface
   input : process.stdin
@@ -15,6 +21,18 @@
       cwd     : __dirname
       fixtures: 'inputs/*.js'
       code    : 'implementations/*.js'
+      format  : (bm) ->
+        name = bold pad 15, bm.name
+        ops  = pad 10, format bm.hz.toFixed(if bm.hz < 100 then 2 else 0)
+        rme  = '\xb1' + bm.stats.rme.toFixed(2)[0...4] + '%'
+        size = pad 2, bm.stats.sample.length
+        ess  = if bm.stats.sample.length is 1 then ' ' else 's'
+        runs = grey 'run' + ess + ' sampled)'
+
+        "#{name} #{dim 'x'} #{ops} #{grey 'ops/sec'} #{rme} #{grey '('}#{size} #{runs}"
+
+      # convert this implementation's file name to its real name.
+      renameKey: (file) -> if file.stem is 'this_library' then '@flatten/array' else file.stem
     }).run()

   else console.log 'quitting'


cleanup help

cleanup help

--- src/scripts/pagerduty.coffee
+++ src/scripts/pagerduty.coffee
@@ -3,43 +3,43 @@
 #
 # Commands:
 #
+#   ## Setup
+#
+#   hubot pager me as <email>                                    remember your pager email is <email>
+#
 #   # Triggering an incident
 #
-#   hubot who's on call                  - return a list of services and who is on call for them
-#   hubot who's on call for <search>     - return the username of who's on call for any schedule matching <search>
-#   hubot pager trigger <user> <msg>     - create a new incident with <msg> and assign it to <user>
-#   hubot pager trigger <schedule> <msg> - create a new incident with <msg> and assign it the user currently on call for <schedule>
-#
-#   ## Setup
-#
-#   hubot pager me as <email> - remember your pager email is <email>
+#   hubot who's on call                                          return a list of services and who is on call for them
+#   hubot who's on call for <search>                             return the username of who's on call for any schedule matching <search>
+#   hubot pager trigger <user> <msg>                             create a new incident with <msg> and assign it to <user>
+#   hubot pager trigger <schedule> <msg>                         create a new incident with <msg> and assign it the user currently on call for <schedule>
 #
 #   # Incident Lifecycle
 #
-#   hubot pager incidents - return the current incidents
-#   hubot pager sup - return the current incidents
-#   hubot pager incident NNN - return the incident NNN
-#   hubot pager note <incident> <content> - add note to incident #<incident> with <content>
-#   hubot pager notes <incident> - show notes for incident #<incident>
-#   hubot pager problems - return all open incidents
-#   hubot pager ack <incident> - ack incident #<incident>
-#   hubot pager ack - ack triggered incidents assigned to you
-#   hubot pager ack! - ack all triggered incidents, not just yours
-#   hubot pager ack <incident1> <incident2> ... <incidentN> - ack all specified incidents
-#   hubot pager resolve <incident> - resolve incident #<incident>
-#   hubot pager resolve <incident1> <incident2> ... <incidentN>- resolve all specified incidents
-#   hubot pager resolve - resolve acknowledged incidents assigned to you
-#   hubot pager resolve! - resolve all acknowledged, not just yours
+#   hubot pager incidents                                        return the current incidents
+#   hubot pager sup                                              return the current incidents
+#   hubot pager incident NNN                                     return the incident NNN
+#   hubot pager note <incident> <content>                        add note to incident #<incident> with <content>
+#   hubot pager notes <incident>                                 show notes for incident #<incident>
+#   hubot pager problems                                         return all open incidents
+#   hubot pager ack <incident>                                   ack incident #<incident>
+#   hubot pager ack                                              ack triggered incidents assigned to you
+#   hubot pager ack!                                             ack all triggered incidents, not just yours
+#   hubot pager ack <incident1> <incident2> ... <incidentN>      ack all specified incidents
+#   hubot pager resolve <incident>                               resolve incident #<incident>
+#   hubot pager resolve <incident1> <incident2> ... <incidentN>  resolve all specified incidents
+#   hubot pager resolve                                          resolve acknowledged incidents assigned to you
+#   hubot pager resolve!                                         resolve all acknowledged, not just yours
 #
 #   ## Scheduling
 #
-#   hubot pager schedules - list schedules
-#   hubot pager schedules <search> - list schedules matching <search>
-#   hubot pager schedule <schedule> - show <schedule>'s shifts for the upcoming month
-#   hubot pager me <schedule> 60 - take the pager for 60 minutes
-#   hubot pager override <schedule> <start> - <end> [username] - Create an schedule override from <start> until <end>. If [username] is left off, defaults to you. start and end should date-parsable dates, like 2014-06-24T09:06:45-07:00, see http://momentjs.com/docs/#/parsing/string/ for examples.
-#   hubot pager overrides <schedule> - show upcoming overrides for the next month
-#   hubot pager override <schedule> delete <id> - delete an override by its ID
+#   hubot pager schedules                                        list schedules
+#   hubot pager schedules <search>                               list schedules matching <search>
+#   hubot pager schedule <schedule>                              show <schedule>'s shifts for the upcoming month
+#   hubot pager me <schedule> 60                                 take the pager for 60 minutes
+#   hubot pager override <schedule> <start> - <end> [username]   Create an schedule override from <start> until <end>. If [username] is left off, defaults to you. start and end should date-parsable dates, like 2014-06-24T09:06:45-07:00, see http://momentjs.com/docs/#/parsing/string/ for examples.
+#   hubot pager overrides <schedule>                             show upcoming overrides for the next month
+#   hubot pager override <schedule> delete <id>                  delete an override by its ID
 #
 # Authors:
 #   Jesse Newland, Josh Nicols, Jacob Bednarz, Chris Lundquist, Chris Streeter, Joseph Pierri, Greg Hoin


add a few more iterations

add a few more iterations

--- src/scripts/pagerduty.coffee
+++ src/scripts/pagerduty.coffee
@@ -373,7 +373,7 @@

   # who is on call?
-  robot.respond /who('s|s| is)? (on call|oncall)( for (.+))?/i, (msg) ->
+  robot.respond /who('s|s| is|se)? (on call|oncall|on-call)( for (.+))?/i, (msg) ->
     query = {}
     if msg.match[4]
       query['query'] = msg.match[4]


ignore hubots own messages

ignore hubots own messages

--- src/adapter.coffee
+++ src/adapter.coffee
@@ -14,7 +14,7 @@
 #    limitations under the License.
 #

-{Adapter} = require 'hubot'
+  {Adapter} = require 'hubot'

 Symphony = require './symphony'
 {V2Message} = require './message'
@@ -82,13 +82,14 @@
           @robot.emit 'error', new Error("Unable to read datafeed #{id}: #{err}")

   _receiveMessage: (message) =>
-    @userLookup(message.fromUserId)
-      .then (response) =>
-        v2 = new V2Message(response, message)
-        @robot.logger.debug "Received '#{v2.text}' from #{v2.user.name}"
-        @receive v2
-      .fail (err) =>
-        @robot.emit 'error', new Error("Unable to fetch user details: #{err}")
+    if message.fromUserId != @robot.userId
+      @userLookup(message.fromUserId)
+        .then (response) =>
+          v2 = new V2Message(response, message)
+          @robot.logger.debug "Received '#{v2.text}' from #{v2.user.name}"
+          @receive v2
+        .fail (err) =>
+          @robot.emit 'error', new Error("Unable to fetch user details: #{err}")

 exports.use = (robot) ->
   new SymphonyAdapter robot


Dynamically generate .apmrc file

Dynamically generate .apmrc file

This ensures the cache path respects the ATOM_HOME env var

--- src/apm-cli.coffee
+++ src/apm-cli.coffee
@@ -21,7 +21,14 @@
     fs.makeTreeSync(temp.dir)
   temp.track()

+setupApmRcFile = ->
+  rcPath = path.resolve(__dirname, '..', '.apmrc')
+  cachePath = path.join(config.getAtomDirectory(), '.apm')
+  try
+    fs.writeFileSync(rcPath, "cache = #{cachePath}\n")
+
 setupTempDirectory()
+setupApmRcFile()

 commandClasses = [
   require './clean'


Removed `navigate_with_fallback` since we don't use it and Backbone provides a fallback itself.

--- core/app/backbone/contrib/navigate_with_fallback.coffee
+++ core/app/backbone/contrib/navigate_with_fallback.coffee
@@ -1,5 +0,0 @@
-Backbone.History.prototype.navigate_with_fallback = (url, args...)->
-  if Backbone.History.started
-    Backbone.history.navigate url, args...
-  else
-    window.location.assign(url);


kite: nodejs kite: remove console.logs

kite: nodejs kite: remove console.logs

--- node_modules_koding/koding-kite/client.coffee
+++ node_modules_koding/koding-kite/client.coffee
@@ -168,8 +168,6 @@
 class KiteEventListener extends EventEmitter
   listener: (request)=>
     [event] = request.args
-    console.error request.args
-    console.error "emitting event", event.action
     @emit event.action, event.kite


Remove excessive debug logging

Remove excessive debug logging

--- routes/postreceive.coffee
+++ routes/postreceive.coffee
@@ -38,8 +38,6 @@
       labels: ['audit']

     requestCb = (err, _res, body) ->
-      console.log err
-      console.log body
       if _res.statusCode != 201
         # error, abort all subsequent calls
         asyncCallback err


Make index accessible to iterator contexts

--- src/binders.coffee
+++ src/binders.coffee
@@ -172,7 +172,7 @@
         @marker.parentNode.removeChild view.els[0]

     for model, index in collection
-      data = {}
+      data = {index}
       data[modelName] = model

       if not @iterated[index]?
@@ -209,7 +209,7 @@

   update: (models) ->
     data = {}
-
+
     for key, model of models
       data[key] = model unless key is @args[0]


middle click to close browser tab.

middle click to close browser tab.

--- salvus/page/editor.coffee
+++ salvus/page/editor.coffee
@@ -583,7 +583,7 @@
             @project_page.display_tab("project-editor")
             @display_tab(path:name)

-        link.find(".salvus-editor-close-button-x").click () =>
+        close_tab = () =>
             if ignore_clicks
                 return false

@@ -622,9 +622,15 @@
             @resize_open_file_tabs()
             return false

+        link.find(".salvus-editor-close-button-x").click(close_tab)
+
         ignore_clicks = false
-        link.find("a").click () =>
+        link.find("a").click (e) =>
             if ignore_clicks
+                return false
+            if e.which==2 or e.ctrlKey
+                # middle (or control-) click on open tab: close the editor
+                close_tab()
                 return false
             open_file(filename)
             return false


Use Point.ZERO instead of allocating a new object

--- src/display-layer.coffee
+++ src/display-layer.coffee
@@ -1094,7 +1094,7 @@
     @displayIndex.getScreenLineCount()

   getRightmostScreenPosition: ->
-    @displayIndex.getScreenPositionWithMaxLineLength() or {row: 0, column: 0}
+    @displayIndex.getScreenPositionWithMaxLineLength() or Point.ZERO

   lineLengthForScreenRow: (screenRow) ->
     @displayIndex.lineLengthForScreenRow(screenRow) or 0


sagews: further uniqueness of uuids

sagews: further uniqueness of uuids

--- src/smc-webapp/sagews.coffee
+++ src/smc-webapp/sagews.coffee
@@ -727,7 +727,7 @@
             start = 0
         if not stop?
             stop = cm.lineCount()-1
-        context = {}
+        context = {uuids:{}}
         for line in [start..stop]
             @_process_line(cm, line, context)

@@ -768,6 +768,11 @@
         switch x[0]
             when MARKERS.cell
                 uuid = x.slice(1, 37)
+                if context.uuids[uuid]
+                    # seen this before -- so change it
+                    uuid = misc.uuid()
+                    cm.replaceRange(uuid, {line:line, ch:1}, {line:line, ch:37})
+                context.uuids[uuid] = true
                 flagstring = x.slice(37, x.length-1)
                 if FLAGS.hide_input in flagstring
                     context.hide = line
@@ -820,6 +825,12 @@
             when MARKERS.output

                 uuid = x.slice(1,37)
+                if context.uuids[uuid]
+                    # seen this before -- so change it
+                    uuid = misc.uuid()
+                    cm.replaceRange(uuid, {line:line, ch:1}, {line:line, ch:37})
+                context.uuids[uuid] = true
+
                 if marks.length == 1 and (marks[0].type != 'output' or marks[0].uuid != uuid)
                     marks[0].clear()
                     marks = []


Add copyAppSkeleton method to sharedHosting Kite

--- kites/sharedHosting/api.coffee
+++ kites/sharedHosting/api.coffee
@@ -75,6 +75,7 @@
               callback? "[ERROR] can't upload file : #{err}"

+
   checkUid:(options,callback)->
     #
     # This methid will check user's uid
@@ -132,7 +133,7 @@

     # options =
     #   username: String #username of the unix user
-    #   uid	: Number # user's UID
+    #   uid : Number # user's UID

     {username,uid} = options
     home = nodePath.join(config.usersPath,username)
@@ -153,6 +154,20 @@
               else
                 log.error error = "[ERROR] couldn't create default vhost for #{username}: #{err}"
                 callback? error
+
+  copyAppSkeleton:(options, callback)->
+    {username, appPath} = options
+
+    appPath = escapePath "#{appPath}/"
+
+    exec "cp -r /opt/Apps/.defaults/* #{appPath}", (err, stdout, stderr)->
+      if not err
+        exec "chown -R #{username}: #{appPath}", (err, stdout, stderr)->
+          console.error err if err
+          callback err
+      else
+        console.error err if err
+        callback err

   publishApp:(options, callback)->


Revert "fix createVhost didn't help"

Revert "fix createVhost didn't help"

This reverts commit c1994396fa0aff2f37445558b4967b6e09eb92b1.

Conflicts:
	kites/sharedHosting/api.coffee

--- kites/sharedHosting/api.coffee
+++ kites/sharedHosting/api.coffee
@@ -78,7 +78,7 @@
         log.info "[OK] user #{username} was secured"
         callback? null,"[OK] user #{username} was secured"

-  buildHome : (options,callback)->
+  buildHome = (options,callback)->
     #
     # this methid will make home directory for user, set correct perms and copy all default files in it
     #
@@ -99,7 +99,7 @@
             log.error error = "[ERROR] can't change owner of home directory to UID/GID #{uid} for user #{username}: #{err}"
             callback error
           else
-            @createVhost username:username,uid:uid,(err,res)->
+            createVhost username:username,uid:uid,(err,res)->
               unless err
                 log.info info = "[OK] default vhost and hosmedir for user #{username} is created"
                 callback? null,info
@@ -201,7 +201,7 @@
                         # now we can build home for user
                         ldapClient.unbind (err)->
                           log.error err if err?
-                        @buildHome username:username,uid:parseInt(id), (error,result)->
+                        buildHome username:username,uid:parseInt(id), (error,result)->
                           if error?
                             callback error
                           else
@@ -225,7 +225,7 @@
   #    else
   #      log.error stderr
   #      callback stderr
-  createVhost : do->
+  createVhost :do->

     spawnWrapper = (command, args , callback)->
       wrapper = spawn command,args
@@ -376,10 +376,4 @@
  #                 else
  #                   log.debug "[OK] func:unSuspendUser: /usr/sbin/cagefsctl -w #{userToSuspend}"
  #                   res = "[OK] user #{userToSuspend} was successfully unsuspended"
- #                   log.info res; callback? null, res
-
-#s = new sharedHosting
-#options =
-#  username:'aleksey-m000010'+hat()
-#  command: "ls -l"
-# s.executeCommand options,(err,res)->
+ #                   log.info res; callback? null, res


Minor clean up

Minor clean up

--- lib/git-commit.coffee
+++ lib/git-commit.coffee
@@ -1,5 +1,3 @@
-# PathWatcher = require 'pathwatcher'
-# File = PathWatcher.File
 fs = require 'fs-plus'
 {BufferedProcess} = require 'atom'
 StatusView = require './status-view'


Fix "styleshet" typo

Fix "styleshet" typo

--- lib/guide-view.coffee
+++ lib/guide-view.coffee
@@ -75,10 +75,10 @@
                 @img class: 'welcome-img', src: 'atom://welcome/assets/code.svg'
               @p '''You can customize almost anything by adding your own CSS/LESS.'''
               @p =>
-                @button outlet: 'stylingButton', class: 'btn btn-primary', 'Open your Styleshet'
+                @button outlet: 'stylingButton', class: 'btn btn-primary', 'Open your Stylesheet'
               @p '''Now uncomment some of the examples or try your own.'''
               @p class: 'welcome-note', =>
-                 @raw 'Next time: You can open your styleshet from Menu > Atom.'
+                 @raw 'Next time: You can open your stylesheet from Menu > Atom.'

           # Init Script
           @details class: 'welcome-card', 'data-section': 'init-script', =>


Renamed del method to delete

Renamed del method to delete

--- lib/jsonserver.coffee
+++ lib/jsonserver.coffee
@@ -44,7 +44,7 @@
   app.put '/:resource/:id', (req, res) ->
     res.jsonp db.update req.params.resource, req.params.id, req.body

-  app.del '/:resource/:id', (req, res) ->
+  app.delete '/:resource/:id', (req, res) ->
     db.remove req.params.resource, req.params.id
     res.jsonp 200, {}


Fix wrap in textbf

Fix wrap in textbf

--- lib/latextools.coffee
+++ lib/latextools.coffee
@@ -235,7 +235,7 @@
     @subscriptions.add atom.commands.add 'atom-text-editor', 'latextools:wrap-in-command': => @snippetManager.wrapInCommand()
     @subscriptions.add atom.commands.add 'atom-text-editor', 'latextools:wrap-in-environment': => @snippetManager.wrapInEnvironment()
     @subscriptions.add atom.commands.add 'atom-text-editor', 'latextools:wrap-in-emph': => @snippetManager.wrapIn("emph")
-    @subscriptions.add atom.commands.add 'atom-text-editor', 'latextools:wrap-in-bold': => @snippetManager.wrapIn("bold")
+    @subscriptions.add atom.commands.add 'atom-text-editor', 'latextools:wrap-in-bold': => @snippetManager.wrapIn("textbf")
     @subscriptions.add atom.commands.add 'atom-text-editor', 'latextools:wrap-in-underline': => @snippetManager.wrapIn("underline")
     @subscriptions.add atom.commands.add 'atom-text-editor', 'latextools:wrap-in-monospace': => @snippetManager.wrapIn("texttt")
     @subscriptions.add atom.commands.add 'atom-text-editor', 'latextools:close-environment': => @snippetManager.closeEnvironment()


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/engine_levels.coffee
+++ src/engine_levels.coffee
@@ -26,7 +26,7 @@
       engine.setState levelState

   ###*
-  Load the
+  Load the map for the

   engine.delay 5, ->


Re-add wesabe.dom.browser with all its methods deprecated.

--- application/chrome/content/wesabe/dom/Browser.coffee
+++ application/chrome/content/wesabe/dom/Browser.coffee
@@ -76,3 +76,23 @@

 module.exports = Browser
+
+# DEPRECATIONS
+#
+# This class used to be a set of functions that were passed a XULBrowser
+# as the first argument, like so:
+#
+#   wesabe.dom.browser.go(browser, "http://www.google.com/"
+#
+# Now it's an actual class that keeps the XULBrowser as an ivar. To help
+# with the transition, this section adds back wesabe.dom.browser.go and
+# friends but yells at you that you're using a deprecated method.
+#
+deprecated = {}
+for name in ['go', 'joinURI', 'atURI']
+  do (name) ->
+    deprecated[name] = (browser, args...) ->
+      logger.deprecated "wesabe.dom.browser.#{name}(browser, ...)", "browser.#{name}(...)"
+      browser[name](args...)
+
+wesabe.provide 'dom.browser', deprecated


display help when no args are passed

display help when no args are passed

--- cli.coffee
+++ cli.coffee
@@ -235,3 +235,6 @@
                     ' to know the list of available commands.'

 program.parse process.argv
+
+unless process.argv.slice(2).length
+    program.outputHelp()


add version command

add version command

--- cli.coffee
+++ cli.coffee
@@ -12,6 +12,7 @@
 filesystem  = require './backend/filesystem'
 binary      = require './backend/binary'
 pouch       = require './backend/db'
+pkg         = require './package.json'

 module.exports.getPassword = (callback) ->
@@ -185,6 +186,10 @@
         log.info 'Unknown command, run "cozy-data-proxy --help"' + \
                  ' to know the list of available commands.'

+program
+    .version(pkg.version)
+
+
 if not module.parent
     program.parse process.argv


add build env values

add build env values

--- src/command.coffee
+++ src/command.coffee
@@ -106,6 +106,11 @@
     @updateWindowsEnv(env) if config.isWin32()
     @addNodeBinToEnv(env)
     @addProxyToEnv(env)
+    env.npm_config_runtime = "electron"
+    env.npm_config_target = @electronVersion
+    env.npm_config_disturl = config.getElectronUrl()
+    env.npm_config_arch = config.getElectronArch()
+    env.npm_config_target_arch = config.getElectronArch() # for node-pre-gyp

   getVisualStudioFlags: ->
     if vsVersion = config.getInstalledVisualStudioFlag()


Fixed regex

Fixed regex

--- src/emoticon-stats.coffee
+++ src/emoticon-stats.coffee
@@ -14,7 +14,7 @@
 # Author:
 #   Ravikiran Janardhana <ravikiran.j.127@gmail.com>

-emoticonRegex = /(^|\s)\([a-z0-9]+\)(\s|$)/g
+emoticonRegex = /\([a-z0-9]+\)/g
 topRegex = /^\/emote top$/i
 bottomRegex = /^\/emote bottom$/i
 emoteCountRegex = /^\/emote\s+(\([a-z0-9]+\))\s*$/i


change condition to toggleClass

change condition to toggleClass

--- src/helpers/class.coffee
+++ src/helpers/class.coffee
@@ -9,8 +9,5 @@
     @listenToOnce @parent, 'render:after', ->
       @watch '_main', (main)->
         for key, value of main
-          if value
-            @$el.addClass key
-          else
-            @$el.removeClass key
+          @$el.toggleClass key, value
         undefined


Don't fail on missing services.

Don't fail on missing services.

--- src/control.coffee
+++ src/control.coffee
@@ -253,6 +253,8 @@
                         success: =>
                             @_addService(service, municipalityIds).done =>
                                 deferred.resolve()
+                        error: =>
+                            deferred.resolve()

         setService: (service) ->
             @services.set []


Use multiple municipalities in API call.

--- src/control.coffee
+++ src/control.coffee
@@ -220,11 +220,13 @@
             unitList = new models.UnitList [], pageSize: PAGE_SIZE, setComparator: true
                 .setFilter('service', service.id)

-            municipality = p13n.get 'city'
-            if municipalityIds? and municipalityIds.length > 0
-                unitList.setFilter 'municipality', municipalityIds[0]
-            else if municipality
-                unitList.setFilter 'municipality', municipality
+            # MunicipalityIds come from explicit query parameters
+            # and they always override the user p13n city setting.
+            unless municipalityIds?
+                # If no explicit parameters received, use p13n profile
+                municipalityIds = p13n.getCities()
+            if municipalityIds.length > 0
+                unitList.setFilter 'municipality', municipalityIds.join(',')

             opts =
                 # todo: re-enable


Update forEach calls to be comprehensions

--- src/hubot/hipchat.coffee
+++ src/hubot/hipchat.coffee
@@ -33,18 +33,18 @@
       if @options.rooms is "@All"
         @get "/v1/rooms/list", (err, response) ->
           if response
-            response.rooms.forEach (room)->
+            for room in response.rooms
               console.log "Joining #{room.xmpp_jid}"
               bot.join room.xmpp_jid
           else
             console.log "Can't list rooms: #{err}"
       else
-        @options.rooms.split(',').forEach (room_id)->
+        for room_id in @options.rooms.split(',')
           console.log "Joining #{room_id}"
           bot.join room_id
       @get "/v1/users/list", (err, response)->
         if response
-          response.users.forEach (user)->
+          for user in response.users
             self.userForId user.user_id, user
         else
           console.log "Can't list rooms: #{err}"


getScopes -> getScopeDescriptor

getScopes -> getScopeDescriptor

--- lib/tag-reader.coffee
+++ lib/tag-reader.coffee
@@ -16,7 +16,7 @@
     return tagsFile if fs.isFileSync(tagsFile)

   find: (editor, callback) ->
-    if editor.getLastCursor().getScopes().indexOf('source.ruby') isnt -1
+    if editor.getLastCursor().getScopeDescriptor().getScopesArray().indexOf('source.ruby') isnt -1
       # Include ! and ? in word regular expression for ruby files
       range = editor.getLastCursor().getCurrentWordBufferRange(wordRegex: /[\w!?]*/g)
     else


Clear blank line on new line

Clear blank line on new line

When a newline is inserted, check to see if the previous line is blank
(only whitespace). If so, decrease the indent of that line to zero. For
this to work, whitespace.removeTrailingWhitespace must be true and
whitespace.ignoreWhitespaceOnlyLines must be false.

--- lib/whitespace.coffee
+++ lib/whitespace.coffee
@@ -31,14 +31,24 @@
         if atom.config.get('whitespace.ensureSingleTrailingNewline', scope: scopeDescriptor)
           @ensureSingleTrailingNewline(editor)

+    editorTextInsertedSubscription = editor.onDidInsertText (event) =>
+      editor.transact =>
+        scopeDescriptor = editor.getRootScopeDescriptor()
+        if atom.config.get('whitespace.removeTrailingWhitespace', scope: scopeDescriptor)
+          unless atom.config.get('whitespace.ignoreWhitespaceOnlyLines', scope: scopeDescriptor)
+            @clearBlankLineOnNewline(editor, event)
+
     editorDestroyedSubscription = editor.onDidDestroy =>
       bufferSavedSubscription.dispose()
+      editorTextInsertedSubscription.dispose()
       editorDestroyedSubscription.dispose()

       @subscriptions.remove(bufferSavedSubscription)
+      @subscriptions.remove(editorTextInsertedSubscription)
       @subscriptions.remove(editorDestroyedSubscription)

     @subscriptions.add(bufferSavedSubscription)
+    @subscriptions.add(editorTextInsertedSubscription)
     @subscriptions.add(editorDestroyedSubscription)

   removeTrailingWhitespace: (editor, grammarScopeName) ->
@@ -87,3 +97,11 @@

     buffer.transact ->
       buffer.scan new RegExp(spacesText, 'g'), ({replace}) -> replace('\t')
+
+  clearBlankLineOnNewline: (editor, event) ->
+    return unless event.text is "\n"
+    buffer = editor.getBuffer()
+    previousRow = editor.getCursorBufferPosition().row - 1
+
+    if buffer.isRowBlank(previousRow)
+      editor.setIndentationForBufferRow(previousRow, 0)


modify whitespace replace fn directly

modify whitespace replace fn directly

--- lib/whitespace.coffee
+++ lib/whitespace.coffee
@@ -4,14 +4,18 @@

   configDefaults:
     ensureSingleTrailingNewline: true
-    ignoredGrammars: ["GitHub Markdown"]

   whitespaceBeforeSave: (editSession) ->
     buffer = editSession.buffer
     buffer.on 'will-be-saved', ->
-      return if editSession.getGrammar().name in config.get("whitespace.ignoredGrammars")
       buffer.transact ->
-        buffer.scan /[ \t]+$/g, ({replace}) -> replace('')
+        regex = null
+
+        buffer.scan /[ \t]+$/g, ({match, replace}) ->
+          # GFM permits two whitespaces at the end of a line--trim anything else
+          unless editSession.getGrammar().scopeName = "scope.gfm" and match[0].length == 2
+            replace('')
+
         if config.get('whitespace.ensureSingleTrailingNewline')
           if buffer.getLastLine() is ''
             row = buffer.getLastRow() - 1


OS detection and Atom native error reporting

--- lib/wp-phptidy.coffee
+++ lib/wp-phptidy.coffee
@@ -1,4 +1,5 @@
 child_process = require 'child_process'
+os            = require 'os'

 module.exports =
 	activate: (state) ->
@@ -12,4 +13,16 @@
 		filepath = file?.path

 		if filepath.length
-		  child_process.exec('php ' + __dirname + '/../vendor/wp-phptidy.php replace ' + filepath)
+			if os.type() is 'Windows_NT'
+				child_process.exec('php ' + __dirname + '/../vendor/wp-phptidy.php replace ' + filepath, {}, (error, stdout, stderror) ->
+					if error?
+						atom.notifications.addError '
+						[WP-PHPTidy] PHP is not installed or there was
+						an error running it.',
+						{dismissable: true}
+				);
+
+			else
+		  		child_process.exec('php ' + __dirname + '/../vendor/wp-phptidy.php replace ' + filepath)
+
+


Hand cursor shows you can turn the cube.

--- app/assets/javascripts/roofpig/DomHandler.js.coffee
+++ app/assets/javascripts/roofpig/DomHandler.js.coffee
@@ -12,7 +12,8 @@

   has_focus: (has_it) ->
     color = if has_it then 'gray' else '#eee'
-    @div.css("border": "2px solid #{color}")
+    cursor = if has_it then 'pointer' else 'default'
+    @div.css("border": "2px solid #{color}", cursor: cursor)

   alg_changed: (playing, at_start, at_end, place_text) ->
     if playing


reverts fix for tinymce

reverts fix for tinymce

--- app/assets/javascripts/webinius_cms/pages.js.coffee
+++ app/assets/javascripts/webinius_cms/pages.js.coffee
@@ -51,4 +51,4 @@
 		]

 $(document).ready(ready)
-$(document).on('page:load', ready)+$(document).on('ready page:load', ready)


div surround status-bar-view and add title to link

--- lib/grammar-status-view.coffee
+++ lib/grammar-status-view.coffee
@@ -4,7 +4,8 @@
 module.exports =
 class GrammarStatusView extends View
   @content: ->
-    @a href: '#', class: 'inline-block'
+    @div class: 'grammar-status', =>
+      @a href: '#', class: 'inline-block'

   initialize: (@statusBar) ->
     @subscribe @statusBar, 'active-buffer-changed', =>
@@ -37,6 +38,9 @@
         grammarName = 'Plain Text'
       else
         grammarName = grammar.name ? grammar.scopeName
-      @text(grammarName).show()
+      @children()
+        .text(grammarName)
+        .attr('title', grammarName)
+        .show()
     else
       @hide()


Update highlight-bad-chars.coffee

Update highlight-bad-chars.coffee

--- lib/highlight-bad-chars.coffee
+++ lib/highlight-bad-chars.coffee
@@ -17,15 +17,14 @@
   # '\xA6', # Broken Bar [Split vertical bar]
   # '\xAB', # Left-Pointing Double Angle Quotation Mark
   # '\xBB', # Right-Pointing Double Angle Quotation Mark
-  '\xBC', # Vulgar Fraction One Quarter
-  '\xBD', # Vulgar Fraction One Half
-  '\xBE', # Vulgar Fraction Three Quarters
+  # '\xBC', # Vulgar Fraction One Quarter
+  # '\xBD', # Vulgar Fraction One Half
+  # '\xBE', # Vulgar Fraction Three Quarters
   # '\xBF', # Inverted Question Mark
   # '\xA8', # Diaeresis [High double dot]
   # '\xB1', # Plus-minus Sign

   # https://www.cs.tut.fi/~jkorpela/chars/spaces.html
-  #'\u00A0', # no-break space
   '\u1680', # ogham space mark
   '\u180E', # mongolian vowel separator
   '\u2000', # en quad
@@ -41,9 +40,9 @@
   '\u200A', # hair space
   '\u200B', # zero width space
   '\u200D', # zero width joiner
-  '\u2013',  # en dash
-  '\u2014',  # em dash
-  '\u2028',  # line separator space
+  '\u2013', # en dash
+  '\u2014', # em dash
+  '\u2028', # line separator space
   '\u202F', # narrow no-break space
   '\u205F', # medium mathematical space
   '\u3000', # ideographic space


project: just SMC_FILENAME, because this variable will be used in other contexts, too

--- src/smc-project/console_server_child.coffee
+++ src/smc-project/console_server_child.coffee
@@ -37,7 +37,7 @@
         filename : undefined

     fn = misc.path_split(opts.filename).tail
-    env = misc.merge({SMC_TERM_FILENAME: fn}, process.env)
+    env = misc.merge({SMC_FILENAME: fn}, process.env)

     term_opts =
         name : 'xterm'


Client: fix regexp for zipcode

Client: fix regexp for zipcode

--- client/Main/payment/paymentmethodentryform.coffee
+++ client/Main/payment/paymentmethodentryform.coffee
@@ -89,7 +89,7 @@
           notifications   : yes
           event           : 'blur'
           rules           :
-            regExp        : /^[0-9]/
+            regExp        : /^[\d-]+$/
           messages        :
             regExp        : 'Zipcode must be a number!'
       captcha             :


Sidebar: Add disableSearch attribute to SidebarSearchModal

--- client/Social/Activity/sidebar/searchmodal.coffee
+++ client/Social/Activity/sidebar/searchmodal.coffee
@@ -25,15 +25,17 @@

   viewAppended: ->

-    {placeholder, noItemText, itemClass} = @getOptions()
-    @addSubView @searchField = new KDInputView
-      placeholder : placeholder
-      cssClass    : 'search-input'
-      keyup       : KD.utils.debounce 300, @bound 'search'
+    {placeholder, noItemText, itemClass, disableSearch} = @getOptions()

-    @addSubView new KDCustomHTMLView
-      tagName  : 'cite'
-      cssClass : 'search-icon'
+    unless disableSearch
+      @addSubView @searchField = new KDInputView
+        placeholder : placeholder
+        cssClass    : 'search-input'
+        keyup       : KD.utils.debounce 300, @bound 'search'
+
+      @addSubView new KDCustomHTMLView
+        tagName  : 'cite'
+        cssClass : 'search-icon'

     @listController = new KDListViewController
       startWithLazyLoader : yes


work on deleteable comments

work on deleteable comments

--- client/landing/app/CommonViews/CommentView.coffee
+++ client/landing/app/CommonViews/CommentView.coffee
@@ -20,7 +20,7 @@
           KDEventTypes  : 'CommentIsDeleted'
           listener      : @
           callback      : ->
-            listView.removeItem view
+          	view.$().html("
This comment has been deleted.
");

     listView.registerListener
       KDEventTypes  : ["AllCommentsLinkWasClicked","CommentInputReceivedFocus"]
@@ -343,19 +343,24 @@
           appManager.tell "Members", "createContentDisplay", origin

   pistachio:->
-    """
-

-      {{> @avatar}}
-

-

-          {{> @author}}
-          {{@utils.applyTextExpansions #(body)}}
-

-        <time>{{$.timeago #(meta.createdAt)}}</time>
-
{{> @settingsButton}}

+    if @getData().getAt 'deletedAt'
+      """
+
This comment has been deleted.

+      """
+    else
+      """
+

+        {{> @avatar}}
+

+

+            {{> @author}}
+            {{@utils.applyTextExpansions #(body)}}
+

+          <time>{{$.timeago #(meta.createdAt)}}</time>
+
{{> @settingsButton}}

+

-

-    """
+      """

   # updatePartial:->
   #   data = @getData()


Hover highlights again as you hover over related annotation.

--- client/lib/annotator/canvas-text-highlight.coffee
+++ client/lib/annotator/canvas-text-highlight.coffee
@@ -224,11 +224,11 @@
     $('.annotations-list .annotation').trigger 'highlightMouseleave', [@annotation._id] if noControl

   _annotationMouseenterHandler: (e, annotationId) =>
-    @hover true if annotationId in _.pluck @annotation.annotations, '_id'
+    @hover true if annotationId in _.pluck @annotation.referencingAnnotations, '_id'
     return # Make sure CoffeeScript does not return anything

   _annotationMouseleaveHandler: (e, annotationId) =>
-    @unhover true if annotationId in _.pluck @annotation.annotations, '_id'
+    @unhover true if annotationId in _.pluck @annotation.referencingAnnotations, '_id'
     return # Make sure CoffeeScript does not return anything

   _createHighlight: =>


neo4j: manually populate array for comments

--- server/lib/server/graph/decorators/base.coffee
+++ server/lib/server/graph/decorators/base.coffee
@@ -66,9 +66,12 @@
     return snapshot

   decorateAdditions:(additions)->
-    results = for addition in additions.slice(-3)
+    results = []
+    for addition in additions.slice(-3)
       addition.bongo_ = {constructorName : addition.name}
       addition.meta   = @decorateSnapshotMeta addition
+
+      results.push addition

     return results


Clear out destination directory before generation. Closes #7

--- src/enfield.coffee
+++ src/enfield.coffee
@@ -178,6 +178,9 @@

 generate = (config, callback) ->
   console.log "Building site: #{config.source} -> #{config.destination}"
+  # Mimic Jekyll behavior by clearing out the destination directory on
+  # generation
+  fs.removeSync config.destination

   # Kick everything off in parallel
   async.parallel(


Disable previously failed partial matches during event replay

--- src/keymap-manager.coffee
+++ src/keymap-manager.coffee
@@ -512,6 +512,15 @@
     {partialMatchCandidates, keydownExactMatchCandidates, exactMatchCandidates} = @findMatchCandidates(@queuedKeystrokes, disabledBindings)
     dispatchedExactMatch = null
     partialMatches = @findPartialMatches(partialMatchCandidates, target)
+
+    # If any partial match *was* pending but has now failed to match, add it to
+    # the list of bindings to disable so we don't attempt to match it again
+    # during a subsequent event replay by `terminatePendingState`.
+    if @pendingPartialMatches?
+      liveMatches = new Set(partialMatches.concat(exactMatchCandidates))
+      for binding in @pendingPartialMatches
+        @bindingsToDisable.push(binding) unless liveMatches.has(binding)
+
     hasPartialMatches = partialMatches.length > 0
     shouldUsePartialMatches = hasPartialMatches


use cell_count vs. split

use cell_count vs. split

--- src/layout/sprites.coffee
+++ src/layout/sprites.coffee
@@ -68,13 +68,16 @@

   go: (world, dir) ->
     console.log 'go', world, dir
-    split = world.get('split')
+    cell_count = world.get('cell_count')
     my.assert dir?, "expects dir"
+    position = world.get('position')
+
     # TODO: invert via direction
-    position = world.get('position')
     direction = world.get('direction')
     sum = vector.add(position, direction)
-    result = vector.bound sum, split, -> world.send 'error'
+
+    # TODO: handle error
+    result = vector.bound sum, cell_count, -> world.send 'error'
     world.put 'position', result

   turn: (world, args) ->


perf: noDelay opt

perf: noDelay opt

--- src/lib/Connection.coffee
+++ src/lib/Connection.coffee
@@ -133,11 +133,8 @@
           @connection = net.connect @connectionOptions.port, @connectionOptions.host
           setupConnectionListeners()

-        if 'setNoDelay' in @connection
+        if @connectionOptions.noDelay
           @connection.setNoDelay()
-
-        if 'socket' in @connection and 'setNoDelay' in @connection.socket
-          @connection.socket.setNoDelay()

         # start listening for timeouts


avoid calling toPlainObject() if submodel is not instance of BaseModel

--- src/lib/base-model.coffee
+++ src/lib/base-model.coffee
@@ -351,12 +351,19 @@

             # strip submodel's relation
             if typeInfo.name is 'MODEL'
-                plainObject[prop] = value.toPlainObject()
+                if value instanceof BaseModel
+                    plainObject[prop] = value.toPlainObject()
+                else
+                    plainObject[prop] = value

             else # typeInfo.name is 'MODELS'
                 plainObject[prop] =
                     for subData in value
-                        subData.toPlainObject()
+                        if subData instanceof BaseModel
+                            subData.toPlainObject()
+                        else
+                            subData
+

         return plainObject


Clarify intent of keyIdentifer to charCode mapping

--- src/helpers.coffee
+++ src/helpers.coffee
@@ -20,9 +20,9 @@

 exports.keystrokeForKeyboardEvent = (event) ->
   unless KeyboardEventModifiers.has(event.keyIdentifier)
-    if event.keyIdentifier.indexOf('U+') is 0
-      hexCharCode = event.keyIdentifier[2..]
-      charCode = charCodeFromHexCharCode(hexCharCode)
+    charCode = charCodeFromKeyIdentifier(event.keyIdentifier)
+    if charCode?
+      charCode = translateCharCodeForLinuxChromiumBug(charCode) if process.platform is 'linux'
       charCode = event.which if not isAscii(charCode) and isAscii(event.which)
       key = keyFromCharCode(charCode)
     else
@@ -120,28 +120,27 @@

   parser.parse(keystroke)

-charCodeFromHexCharCode = (hexCharCode) ->
-  charCode = parseInt(hexCharCode, 16)
+charCodeFromKeyIdentifier = (keyIdentifier) ->
+  parseInt(keyIdentifier[2..], 16) if keyIdentifier.indexOf('U+') is 0

-  # Chromium includes incorrect keyIdentifier values on keypress events for
-  # certain symbols keys on Linux.
-  #
-  # See https://code.google.com/p/chromium/issues/detail?id=51024
-  # See https://bugs.webkit.org/show_bug.cgi?id=19906
-  if process.platform is 'linux'
-    switch charCode
-      when 186 then charCode = 59 # ";"
-      when 187 then charCode = 61 # "="
-      when 188 then charCode = 44 # ","
-      when 189 then charCode = 45 # "-"
-      when 190 then charCode = 46 # "."
-      when 191 then charCode = 47 # "/"
-      when 219 then charCode = 91 # "["
-      when 220 then charCode = 92 # "\"
-      when 221 then charCode = 93 # "]"
-      when 222 then charCode = 39 # "'"
-
-  charCode
+# Chromium includes incorrect keyIdentifier values on keypress events for
+# certain symbols keys on Linux.
+#
+# See https://code.google.com/p/chromium/issues/detail?id=51024
+# See https://bugs.webkit.org/show_bug.cgi?id=19906
+translateCharCodeForLinuxChromiumBug = (charCode) ->
+  switch charCode
+    when 186 then charCode = 59 # ";"
+    when 187 then charCode = 61 # "="
+    when 188 then charCode = 44 # ","
+    when 189 then charCode = 45 # "-"
+    when 190 then charCode = 46 # "."
+    when 191 then charCode = 47 # "/"
+    when 219 then charCode = 91 # "["
+    when 220 then charCode = 92 # "\"
+    when 221 then charCode = 93 # "]"
+    when 222 then charCode = 39 # "'"
+    else charCode

 keyFromCharCode = (charCode) ->
   switch charCode


Fix tests in node v7.

Fix tests in node v7.

--- test/ClientTest.coffee
+++ test/ClientTest.coffee
@@ -23,24 +23,34 @@
     close: ->

 describe 'Client', ->
+    sandbox = null
+
+    beforeEach ->
+        sandbox = sinon.sandbox.create()
+
+    afterEach ->
+        sandbox.restore()
+
     it 'should group together dropped messages if they come together too quickly', (done) ->
-        dropCount = 0
+        dropEventCount = 0

         client = new Client {}, {
             minTimeBetweenDropEvents: 10,
             _connect: -> return new DropSocket()
         }

+        sinon.stub Date, "now", -> 1481732966204
+
         client.on 'disconnect', -> done new Error "Should not disconnect."

         client.on 'dropped', (count) ->
             try
-                dropCount++
+                dropEventCount++

-                if dropCount is 1
+                if dropEventCount is 1
                     # Drop a single message for the first message
                     expect(count).to.equal 1
-                if dropCount is 2
+                if dropEventCount is 2
                     # But we should see the next two messages grouped into a single "dropped" event.
                     expect(count).to.equal 2
                     done()


rename file

rename file

--- app/assets/javascripts/darkswarm/directives/enterproise_modal.js.coffee
+++ app/assets/javascripts/darkswarm/directives/enterproise_modal.js.coffee
@@ -1,8 +0,0 @@
-Darkswarm.directive "enterpriseModal", ($modal)->
-  restrict: 'E'
-  replace: true
-  template: "<a>{{enterprise.name}}</a>"
-  link: (scope, elem, attrs, ctrl)->
-    elem.on "click", (ev)=>
-      ev.stopPropagation()
-      scope.modalInstance = $modal.open(controller: ctrl, templateUrl: 'hub_modal.html', scope: scope)


Fix deep features call for DL model predictions.

--- src/ext/components/predict-input.coffee
+++ src/ext/components/predict-input.coffee
@@ -111,8 +111,9 @@
     if _hasReconError()
       if _computeReconstructionError()
         cs += ', reconstruction_error: true'
-      else if _computeDeepFeaturesHiddenLayer()
-        cs += ", deep_features_hidden_layer: #{_deepFeaturesHiddenLayerValue()}"
+
+    if _computeDeepFeaturesHiddenLayer()
+      cs += ", deep_features_hidden_layer: #{_deepFeaturesHiddenLayerValue()}"

     if _hasLeafNodeAssignment()
       if _computeLeafNodeAssignment()


Cleanup

Cleanup

--- src/js/ogl-browse.coffee
+++ src/js/ogl-browse.coffee
@@ -42,7 +42,6 @@
   repo_li.append($('<p>').append(archive_link))
   loader = ($('<div>').attr('class','ui active mini loader'))
   repo_li.append(loader)
-
   $.ajax "https://openlibrary.org/ia/#{identifier}.json",
     type: 'GET'
     dataType: 'json'
@@ -87,12 +86,9 @@
     error: (jqXHR, textStatus, errorThrown) ->
       console.log('AJAX error')
     success: (data, textStatus, jqXHR) ->
-      console.log('AJAX success')
       console.log(jqXHR.getResponseHeader('Link'))
       links = jqXHR.getResponseHeader('Link').split(',')
       next = (link.split(';')[0] for link in links when link.split(';')[1] is ' rel="next"')
-      console.log(next)
-      console.log(data)
       repos = repos.concat(data)
       if next.length > 0
         grab_repo_page(next[0][1..-2], callback)


also gen for browser

also gen for browser

--- test/browser/main.iced
+++ test/browser/main.iced
@@ -1,5 +1,6 @@

 mods =
+  revoked_subkeys : require '../files/revoked_subkeys.iced'
   sigs : require "../files/verify_sigs.iced"
   multiples : require "../files/multiples.iced"
   unbox_cant_verify : require '../files/unbox_cant_verify.iced'


fix examples

fix examples

--- components/UploadedFile/UploadedFileExamples.cjsx
+++ components/UploadedFile/UploadedFileExamples.cjsx
@@ -2,7 +2,7 @@

 UploadedFile = require './UploadedFile'
 React        = require 'react'
-imageSrc     = 'data:image/gif;base64,R0lGODlhEAAQAMQAAORHHOVSKudfOulrSOp3WOyDZu6QdvCchPGolfO0o/XBs/fNwfjZ0frl3/zy7////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAkAABAALAAAAAAQABAAAAVVICSOZGlCQAosJ6mu7fiyZeKqNKToQGDsM8hBADgUXoGAiqhSvp5QAnQKGIgUhwFUYLCVDFCrKUE1lBavAViFIDlTImbKC5Gm2hB0SlBCBMQiB0UjIQA7'
+preview     = 'data:image/gif;base64,R0lGODlhEAAQAMQAAORHHOVSKudfOulrSOp3WOyDZu6QdvCchPGolfO0o/XBs/fNwfjZ0frl3/zy7////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAkAABAALAAAAAAQABAAAAVVICSOZGlCQAosJ6mu7fiyZeKqNKToQGDsM8hBADgUXoGAiqhSvp5QAnQKGIgUhwFUYLCVDFCrKUE1lBavAViFIDlTImbKC5Gm2hB0SlBCBMQiB0UjIQA7'

 UploadedFileExamples = ->

@@ -20,7 +20,7 @@
       status="uploading"
       fileName="crochet-turtle.jpg"
       progress="50"
-      imageSrc={imageSrc}
+      preview={preview}
       />

File is uploading at 50 progress


set x and y properties

set x and y properties

--- src/layout/sprite.coffee
+++ src/layout/sprite.coffee
@@ -35,8 +35,8 @@
   # defaults
   position:  [0,0]
   direction: [1,0]
-  x: (world) -> 0
-  y: (world) -> 0
+  x: (world) -> world.get('scale') * position.at(vector.axis.x)
+  y: (world) -> world.get('scale') * position.at(vector.axis.y)

   name_style: (world) ->
     scale = world.get 'scale'


Treat old WirePattern as legacy

Treat old WirePattern as legacy

--- src/lib/Component.coffee
+++ src/lib/Component.coffee
@@ -145,8 +145,6 @@
   isLegacy: ->
     # Process API
     return false if @handle
-    # WirePattern
-    return false if @_wpData
     # Legacy
     true


update untuk jwt

update untuk jwt

--- Server/app.coffee
+++ Server/app.coffee
@@ -4,12 +4,12 @@
 morgan = require 'morgan'
 methodOverride = require 'method-override'
 expressSession = require 'express-session'
+passport = require 'passport'
 bodyParser = require 'body-parser'
 errorhandler = require 'errorhandler'
 mongoose = require 'mongoose'
 cookieParser = require 'cookie-parser'
 csrf = require 'csurf'
-jwt = require 'jsonwebtoken'
 expressJwt = require 'express-jwt'
 Logger = require './utils/Logger'
 BarangRoute = require './routes/BarangRoute'
@@ -29,6 +29,8 @@
     next()

 app.use morgan('combined', stream: Logger.stream)
+app.use passport.initialize()
+app.use passport.session()

 app.use methodOverride()
 app.use expressSession
@@ -40,7 +42,12 @@
 app.use bodyParser.urlencoded
     extended: true

+app.use '/api', expressJwt
+    secret: 'rizki'
 app.use '/api', BarangRoute
+app.use (err, req, res, next) ->
+    if err.name == 'UnauthorizedError'
+        res.status(401).send 'Unauthorized'

 mongoose.connect 'mongodb://localhost/BelajarES6', (err) ->
     if err


Guarding webview attributes against polluted Object.prototypes

Ran into an error with the buildParams function breaking when using a JavaScript library that modified the Object.prototype, this small fix resolves the issue.

--- atom/renderer/lib/web-view/web-view.coffee
+++ atom/renderer/lib/web-view/web-view.coffee
@@ -173,7 +173,7 @@
     params =
       instanceId: @viewInstanceId
       userAgentOverride: @userAgentOverride
-    for attributeName, attribute of @attributes
+    for own attributeName, attribute of @attributes
       params[attributeName] = attribute.getValue()
     #  When the WebView is not participating in layout (display:none)
     #  then getBoundingClientRect() would report a width and height of 0.


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/number_extensions.coffee
+++ test/number_extensions.coffee
@@ -114,6 +114,6 @@
     Point()
   ]
   4.circularPoints (p, i) ->
-    points[i]
+    opoints[i]

 module undefined


:apple: Fix emoji rendering

:apple: Fix emoji rendering

--- src/workspace-element.coffee
+++ src/workspace-element.coffee
@@ -44,10 +44,15 @@
     @subscriptions.add @config.onDidChange 'editor.lineHeight', @updateGlobalTextEditorStyleSheet.bind(this)

   updateGlobalTextEditorStyleSheet: ->
+    fontFamily = @config.get('editor.fontFamily')
+    # TODO: There is a bug in how some emojis (e.g. ❤) are rendered on OSX.
+    # This workaround should be removed once we update to Chromium 51, where the
+    # problem was fixed.
+    fontFamily += ', "Apple Color Emoji"' if process.platform is 'darwin'
     styleSheetSource = """
       atom-text-editor {
         font-size: #{@config.get('editor.fontSize')}px;
-        font-family: #{@config.get('editor.fontFamily')};
+        font-family: #{fontFamily};
         line-height: #{@config.get('editor.lineHeight')};
       }
     """


check that a message isn't sent when the whitelist prevents it

--- test/integration/send-message-2-spec.coffee
+++ test/integration/send-message-2-spec.coffee
@@ -64,6 +64,13 @@

     @devices.insert @spyDevice, done

+  beforeEach 'create nsa device', (done) ->
+    @nsaDevice =
+      uuid: 'nsa-uuid'
+      type: 'device:nsa'
+
+    @devices.insert @nsaDevice, done
+
   context 'When sending a message to another device', ->
     context "sender-uuid receiving its sent messages", ->
       @timeout 5000
@@ -179,3 +186,42 @@

       it 'should deliver the sent message to the receiver', ->
         expect(@message).to.exist
+
+    context 'subscribed to someone elses received messages, but is not authorized', ->
+      @timeout 5000
+      beforeEach 'create message sent subscription', (done) ->
+        subscription =
+          type: 'message.sent'
+          emitterUuid: 'sender-uuid'
+          subscriberUuid: 'nsa-uuid'
+
+        @subscriptions.insert subscription, done
+
+      beforeEach 'create message received subscription', (done) ->
+        subscription =
+          type: 'message.received'
+          emitterUuid: 'nsa-uuid'
+          subscriberUuid: 'nsa-uuid'
+
+        @subscriptions.insert subscription, done
+
+      beforeEach (done) ->
+        job =
+          metadata:
+            auth: @auth
+            toUuid: @auth.uuid
+            jobType: 'SendMessage2'
+          rawData: JSON.stringify devices:['receiver-uuid'], payload: 'boo'
+
+        client = new RedisNS 'messages', redis.createClient(@redisUri)
+        @hydrant = new HydrantManager {client, @uuidAliasResolver}
+        @hydrant.connect uuid: 'nsa-uuid', (error) =>
+          return done(error) if error?
+
+          @hydrant.once 'message', (@message) => @hydrant.close()
+
+          @dispatcher.generateJobs job, (error, @generatedJobs) =>
+            setTimeout done, 2000
+
+      it 'should not deliver the sent message to the receiver', ->
+        expect(@message).to.not.exist


More fixes for unit tests.

More fixes for unit tests.

--- test/unit/strategies/oauth2/userInfo.coffee
+++ test/unit/strategies/oauth2/userInfo.coffee
@@ -5,6 +5,7 @@
 sinon     = require 'sinon'
 sinonChai = require 'sinon-chai'
 expect    = chai.expect
+util      = require 'util'

@@ -157,11 +158,11 @@
       scope    = nock(provider.url)
                    .get('/user?' + auth.query + '=' + token)
                    .reply(200, { fullname: 'Dude' })
-      req = strategy.userInfo token, -> done()
+      req = strategy.userInfo token, () -> done()
       return

     it 'should set a custom parameter', ->
-      req.qsRaw.should.contain 'oauth_token=r4nd0m'
+      req.req.path.should.contain 'oauth_token=r4nd0m'

@@ -181,7 +182,7 @@
       return

     it 'should set a custom parameter', ->
-      req.qs.foo.should.equal 'bar'
+      req.req.path.should.contain 'foo=bar'

@@ -198,7 +199,7 @@
       strategy = new OAuth2Strategy provider, client, verifier

       scope = nock(provider.url)
-        .get('/user?foo=bar&entropy=t0k3n')
+        .get('/user?entropy=t0k3n&foo=bar')
         .reply(400, { error: 'oops' })

       req = strategy.userInfo 't0k3n', (error, response) ->
@@ -226,7 +227,7 @@
       strategy = new OAuth2Strategy provider, client, verifier

       scope = nock(provider.url)
-        .get('/user?foo=bar&entr0py=t0k3n')
+        .get('/user?entr0py=t0k3n&foo=bar')
         .reply(200, { uid: 1234, fullname: 'Yoda' })

       strategy.userInfo 't0k3n', (error, response) ->
@@ -243,7 +244,3 @@

     it 'should normalize the provider user id', ->
       res.id.should.equal '1234'
-
-
-
-


Move towards a non-jqueryish style

Move towards a non-jqueryish style

--- drop.coffee
+++ drop.coffee
@@ -1,6 +1,6 @@
 ###

-Drop - Finally a dropdown which understands where it is.
+drop - Finally a dropdown which understands where it is.

     - Attach to 8 different locations
     - Attach options diagram:
@@ -61,292 +61,265 @@
     $el.attr 'class', (index, className) ->
         className.replace(new RegExp("\\b#{ prefix }\\S+", 'g'), '').replace(/\s+/g, ' ')

+allDrops = []
+
+# Drop can be included in external libraries.  Calling createContext gives you a fresh
+# copy of drop which won't interact with other copies on the page (beyond calling the document events).
+createContext = (options) ->
+    drop = ->
+        new DropInstance arguments...
+
+    $.extend drop,
+        createContext: createContext
+        drops: []
+
+    defaults =
+        classNames:
+            drop: 'drop'
+            dropContent: 'drop-content'
+            opened: 'drop-opened'
+            closed: 'drop-closed'
+            allClosed: 'drop-all-closed'
+            attachPrefix: 'drop-attached-'
+
+        defaults:
+            attach: 'bottom-left'
+            trigger: 'click'
+            constrainToScrollParent: true
+            constrainToWindow: true
+            className: ''
+            closedOnInit: true
+            content: 'drop'
+
+    $.extend true, drop, defaults, options
+
+    $(document).on 'dropopen.drop, dropclose.drop', -> drop.updateBodyClasses()
+
+    $(window).on 'resize', debounce(-> drop.positionAll())
+    $(window).on 'scroll.drop', debounce(-> drop.positionAll())
+
+    drop.positionAll = ->
+        for drop in drop.drops when drop.isOpened()
+            drop.position()
+
+    drop.updateBodyClasses = ->
+        # There is only one body, so despite the context concept, we still iterate through all
+        # drops created in any context before applying the class.
+
+        anyOpen = false
+        for drop in allDrops when drop.isOpened()
+            anyOpen = true
+            break
+
+        if anyOpen
+            $('body').addClass(drop.classNames.opened).removeClass(drop.classNames.allClosed)
+        else
+            $('body').removeClass(drop.classNames.opened).addClass(drop.classNames.allClosed)
+
+    class DropInstance
+        constructor: (@options) ->
+            @options = $.extend {}, @options.defaults, @options
+
+            @$target = $ @options.target
+
+            drop.drops.push @
+            allDrops.push @
+
+            if @options.attach and not (@options.attachFirst or @options.attachSecond)
+                attachSplit = options.attach.split('-')
+                @options.attachFirst = attachSplit[0]
+                @options.attachSecond = attachSplit[1]
+
+            @setupElements()
+            @setupEvents()
+
+        setupElements: ->
+            @$drop = $ '<div>'
+            @$drop.addClass drop.classNames.drop
+            @$drop.addClass options.className
+
+            @$dropContent = $ '<div>'
+            @$dropContent.addClass drop.classNames.dropContent
+            @$dropContent.append @options.content
+
+            @$drop.append @$dropContent
+
+            @attach @options.attachFirst, @options.attachSecond
+
+            if @options.closedOnInit
+                @$drop.addClass drop.classNames.closed
+
+        setupEvents: ->
+            $scrollParent = scrollParent(@$target)
+
+            position = => @position() if @isOpened()
+
+            $scrollParent.on 'scroll.drop', debounce(position)
+
+            if @options.trigger is 'click'
+                @$target.bind 'click.drop', => @toggle()
+
+                $(document).bind 'click.drop', (event) =>
+                    return unless @isOpened
+
+                    # Clicking inside dropdown
+                    if $(event.target).is(@$drop[0]) or @$drop.find(event.target).length
+                        return
+
+                    # Clicking target
+                    if $(event.target).is(@$target[0]) or @$target.find(event.target).length
+                        return
+
+                    @close()
+
+        isOpened: ->
+            @$drop.hasClass(drop.classNames.opened)
+
+        toggle: ->
+            if @isOpened
+                @close()
+            else
+                @open()
+
+        open: ->
+            unless @$drop.parent().length
+                $('body').append @$drop
+
+            @position()
+
+            @$target
+                .addClass(drop.classNames.opened)
+                .removeClass(drop.classNames.closed)
+
+            @$drop
+                .addClass(drop.classNames.opened)
+                .removeClass(drop.classNames.closed)
+
+            @$drop.trigger
+                type: 'dropopen'
+                drop: @
+
+        close: ->
+            @$target
+                .addClass(drop.classNames.closed)
+                .removeClass(drop.classNames.opened)
+
+            @$drop
+                .addClass(drop.classNames.closed)
+                .removeClass(drop.classNames.opened)
+
+            @$drop.trigger
+                type: 'dropclose'
+                drop: @
+
+        attach: (attachFirst, attachSecond) ->
+            $([@$target[0], @$drop[0]]).each ->
+                $el = $(@)
+                removePrefixedClasses($el, drop.classNames.attachPrefix)
+                $el.addClass("#{ drop.classNames.attachPrefix }#{ attachFirst }-#{ attachSecond }")
+
+        position: ->
+            targetOffset = @$target.offset()
+            $scrollParent = scrollParent(@$target)
+            scrollParentOffset = @$scrollParent.offset()
+
+            targetOuterHeight = @$target.outerHeight()
+            targetOuterWidth = @$target.outerWidth()
+
+            dropOuterHeight = @$drop.outerHeight()
+            dropOuterWidth = @$drop.outerWidth()
+
+            windowScrollTop = $(window).scrollTop()
+            windowScrollLeft = $(window).scrollLeft()
+
+            # Above and below target
+
+            if @options.attachFirst is 'bottom'
+                top = targetOffset.top + targetOuterHeight
+
+            if @options.attachFirst is 'top'
+                top = targetOffset.top - dropOuterHeight
+
+            if @options.attachSecond is 'left'
+                left = targetOffset.left
+
+            if @options.attachSecond is 'right'
+                left = targetOffset.left + targetOuterWidth - dropOuterWidth
+
+            # Left and right of target
+
+            if @options.attachFirst is 'right'
+                left = targetOffset.left + targetOuterWidth
+
+            if @options.attachFirst is 'left'
+                left = targetOffset.left - dropOuterWidth
+
+            if @options.attachSecond is 'top'
+                top = targetOffset.top
+
+            if @options.attachSecond is 'bottom'
+                top = targetOffset.top + targetOuterHeight - dropOuterHeight
+
+            # Constraints
+
+            unless $scrollParent.is('html')
+                if @options.constrainToScrollParent or @options.attachFirst in ['left', 'right']
+                    top = Math.min(Math.max(top, scrollParentOffset.top), scrollParentOffset.top + $scrollParent.outerHeight() - dropOuterHeight)
+
+                if @options.constrainToScrollParent or @options.attachFirst in ['top', 'bottom']
+                    left = Math.min(Math.max(left, scrollParentOffset.left), scrollParentOffset.left + $scrollParent.outerWidth() - dropOuterWidth)
+
+            if @options.constrainToWindow
+                wasConstrained = false
+
+                topMin = windowScrollTop
+                topMax = $(window).height() + windowScrollTop - dropOuterHeight
+
+                leftMin = windowScrollLeft
+                leftMax = $(window).width() + windowScrollLeft - dropOuterWidth
+
+                if @options.attachFirst in ['top', 'bottom']
+                    if top < topMin
+                        wasConstrained = true
+                        top = topMin
+                        top = targetOffset.top + targetOuterHeight
+                        @attach 'bottom', @options.attachSecond
+
+                    if top > topMax
+                        wasConstrained = true
+                        top = topMax
+                        top = targetOffset.top - dropOuterHeight
+                        @attach 'top', @options.attachSecond
+
+                if @options.attachFirst in ['left', 'right']
+                    if left < leftMin
+                        wasConstrained = true
+                        left = leftMin
+                        left = targetOffset.left + targetOuterWidth
+                        @attach 'right', @options.attachSecond
+
+                    else if left > leftMax
+                        wasConstrained = true
+                        left = leftMax
+                        left = targetOffset.left - dropOuterWidth
+                        @attach 'left', @options.attachSecond
+
+                unless wasConstrained
+                    @attach @options.attachFirst, @options.attachSecond
+
+                top = Math.min(Math.max(top, topMin), topMax)
+                left = Math.min(Math.max(left, leftMin), leftMax)
+
+            top += 'px'
+            left += 'px'
+
+            unless @$drop.css('top') is top and @$drop.css('left') is left
+                @$drop[0].style.top = top
+                @$drop[0].style.left = left
+
+    drop
+
+window.Drop = createContext()
+
 $ ->
     Drop.updateBodyClasses()
-
-    $(document).on 'openDrop.drop, closeDrop.drop', (event) -> Drop.updateBodyClasses()
-
-    $(window).on 'resize', debounce(-> Drop.positionAll())
-    $(window).on 'scroll.drop', debounce(-> Drop.positionAll())
-
-Drop =
-    baseClassNames:
-        drop: 'drop'
-        dropContent: 'drop-content'
-        opened: 'drop-opened'
-        closed: 'drop-closed'
-        allClosed: 'drop-all-closed'
-        attachPrefix: 'drop-attached-'
-
-    defaults:
-        attach: 'bottom-left'
-        trigger: 'click'
-        constrainToScrollParent: true
-        constrainToWindow: true
-        className: ''
-        closedOnInit: true
-        content: 'drop'
-        prerender: false # TODO - this would allow drop to be rendered before being opened
-
-    dropTargets: []
-
-    positionAll: ->
-        $.each Drop.dropTargets, (i, $target) ->
-            if $target.drop 'isOpened'
-                $target.drop 'positionDrop'
-
-    updateBodyClasses: ->
-        anyOpen = false
-
-        $.each Drop.dropTargets, (i, $target) ->
-            if $target.drop 'isOpened'
-                anyOpen = true
-                return false
-
-        if anyOpen
-            $('body').addClass(Drop.baseClassNames.opened).removeClass(Drop.baseClassNames.allClosed)
-        else
-            $('body').removeClass(Drop.baseClassNames.opened).addClass(Drop.baseClassNames.allClosed)
-
-jQueryMethods =
-    init: (opts) -> @each ->
-        $target = $ @
-        Drop.dropTargets.push $target
-
-        options = $.extend {}, Drop.defaults, opts
-
-        if options.attach and not (options.attachFirst or options.attachSecond)
-            attachSplit = options.attach.split('-')
-            options.attachFirst = attachSplit[0]
-            options.attachSecond = attachSplit[1]
-
-        $target.data 'drop', options
-
-        $target.drop 'setupElements'
-        $target.drop 'setupEvents'
-
-    setupElements: ->
-        $target = $ @
-        options = $target.data('drop')
-
-        options.$drop = $ '<div>'
-        options.$drop.addClass Drop.baseClassNames.drop
-        options.$drop.addClass options.className
-
-        options.$dropContent = $ '<div>'
-        options.$dropContent.addClass Drop.baseClassNames.dropContent
-        options.$dropContent.append options.content
-
-        options.$drop.append options.$dropContent
-
-        $target.drop 'attach', options.attachFirst, options.attachSecond
-
-        if options.closedOnInit
-            options.$drop.addClass Drop.baseClassNames.closed
-
-        $target
-
-    setupEvents: ->
-        $target = $ @
-        options = $target.data('drop')
-
-        $scrollParent = scrollParent($target)
-
-        position = -> $target.drop 'positionDrop' if $target.drop 'isOpened'
-
-        $scrollParent.on 'scroll.drop', debounce(position)
-
-        if options.trigger is 'click'
-            $target.bind 'click.drop', -> $target.drop 'toggle'
-
-            $(document).bind 'click.drop', (event) ->
-                return unless $target.drop 'isOpened'
-
-                # Clicking inside dropdown
-                if $(event.target).is(options.$drop[0]) or options.$drop.find(event.target).length
-                    return
-
-                # Clicking target
-                if $(event.target).is($target[0]) or $target.find(event.target).length
-                    return
-
-                $target.drop 'close'
-
-        $target
-
-    toggle: ->
-        $target = $ @
-        options = $target.data('drop')
-
-        if $target.drop 'isOpened'
-            $target.drop 'close'
-        else
-            $target.drop 'open'
-
-        $target
-
-    isOpened: ->
-        $(@).data('drop').$drop.hasClass(drop.baseClassNames.opened)
-
-    open: ->
-        $target = $ @
-        options = $target.data('drop')
-
-        unless options.$drop.parent().length
-            $('body').append options.$drop
-
-        $target.drop 'positionDrop'
-
-        $target
-            .addClass(Drop.baseClassNames.opened)
-            .removeClass(Drop.baseClassNames.closed)
-
-        options.$drop
-            .addClass(Drop.baseClassNames.opened)
-            .removeClass(Drop.baseClassNames.closed)
-
-        options.$drop.trigger
-            type: 'openDrop'
-            $drop: $target
-
-        $target
-
-    close: ->
-        $target = $ @
-        options = $target.data('drop')
-
-        $target
-            .addClass(Drop.baseClassNames.closed)
-            .removeClass(Drop.baseClassNames.opened)
-
-        options.$drop
-            .addClass(Drop.baseClassNames.closed)
-            .removeClass(Drop.baseClassNames.opened)
-
-        options.$drop.trigger
-            type: 'closeDrop'
-            $drop: $target
-
-        $target
-
-    attach: (attachFirst, attachSecond) ->
-        $target = $ @
-        options = $target.data('drop')
-
-        $([$target[0], options.$drop[0]]).each ->
-            $el = $(@)
-            removePrefixedClasses($el, Drop.baseClassNames.attachPrefix)
-            $el.addClass("#{ Drop.baseClassNames.attachPrefix }#{ attachFirst }-#{ attachSecond }")
-
-    positionDrop: ->
-        $target = $ @
-        options = $target.data('drop')
-
-        targetOffset = $target.offset()
-        $scrollParent = scrollParent($target)
-        scrollParentOffset = $scrollParent.offset()
-
-        targetOuterHeight = $target.outerHeight()
-        targetOuterWidth = $target.outerWidth()
-
-        dropOuterHeight = options.$drop.outerHeight()
-        dropOuterWidth = options.$drop.outerWidth()
-
-        windowScrollTop = $(window).scrollTop()
-        windowScrollLeft = $(window).scrollLeft()
-
-        # Above and below target
-
-        if options.attachFirst is 'bottom'
-            top = targetOffset.top + targetOuterHeight
-
-        if options.attachFirst is 'top'
-            top = targetOffset.top - dropOuterHeight
-
-        if options.attachSecond is 'left'
-            left = targetOffset.left
-
-        if options.attachSecond is 'right'
-            left = targetOffset.left + targetOuterWidth - dropOuterWidth
-
-        # Left and right of target
-
-        if options.attachFirst is 'right'
-            left = targetOffset.left + targetOuterWidth
-
-        if options.attachFirst is 'left'
-            left = targetOffset.left - dropOuterWidth
-
-        if options.attachSecond is 'top'
-            top = targetOffset.top
-
-        if options.attachSecond is 'bottom'
-            top = targetOffset.top + targetOuterHeight - dropOuterHeight
-
-        # Constraints
-
-        unless $scrollParent.is('html')
-            if options.constrainToScrollParent or options.attachFirst in ['left', 'right']
-                top = Math.min(Math.max(top, scrollParentOffset.top), scrollParentOffset.top + $scrollParent.outerHeight() - dropOuterHeight)
-
-            if options.constrainToScrollParent or options.attachFirst in ['top', 'bottom']
-                left = Math.min(Math.max(left, scrollParentOffset.left), scrollParentOffset.left + $scrollParent.outerWidth() - dropOuterWidth)
-
-        if options.constrainToWindow
-            wasConstrained = false
-
-            topMin = windowScrollTop
-            topMax = $(window).height() + windowScrollTop - dropOuterHeight
-
-            leftMin = windowScrollLeft
-            leftMax = $(window).width() + windowScrollLeft - dropOuterWidth
-
-            if options.attachFirst in ['top', 'bottom']
-                if top < topMin
-                    wasConstrained = true
-                    top = topMin
-                    top = targetOffset.top + targetOuterHeight
-                    $target.drop('attach', 'bottom', options.attachSecond)
-
-                if top > topMax
-                    wasConstrained = true
-                    top = topMax
-                    top = targetOffset.top - dropOuterHeight
-                    $target.drop('attach', 'top', options.attachSecond)
-
-            if options.attachFirst in ['left', 'right']
-                if left < leftMin
-                    wasConstrained = true
-                    left = leftMin
-                    left = targetOffset.left + targetOuterWidth
-                    $target.drop('attach', 'right', options.attachSecond)
-
-                else if left > leftMax
-                    wasConstrained = true
-                    left = leftMax
-                    left = targetOffset.left - dropOuterWidth
-                    $target.drop('attach', 'left', options.attachSecond)
-
-            $target.drop('attach', options.attachFirst, options.attachSecond) unless wasConstrained
-
-            top = Math.min(Math.max(top, topMin), topMax)
-            left = Math.min(Math.max(left, leftMin), leftMax)
-
-        top += 'px'
-        left += 'px'
-
-        unless options.$drop.css('top') is top and options.$drop.css('left') is left
-            options.$drop[0].style.top = top
-            options.$drop[0].style.left = left
-
-        $target
-
-window.Drop = Drop
-
-$.fn.drop = (options) ->
-    if jQueryMethods[options]
-        jQueryMethods[options].apply this, Array::slice.call(arguments, 1)
-    else if typeof options is 'object' or not options
-        jQueryMethods.init.apply this, arguments
-    else
-        $.error "jQuery.drop: Method #{ options } does not exist"


Fix indent

Fix indent

--- test/goban.core.coffee
+++ test/goban.core.coffee
@@ -85,26 +85,26 @@
             color = func()
             expect(color).to.equal(empty)

-      describe "with incorrect external color", ->
-        it "should throw error", ->
+    describe "with incorrect external color", ->
+      it "should throw error", ->

-          config = test_board.config()
-          empty = config['stone']['EMPTY']
-          expect(config['coordinate_system']).to.equal('cartesian_one')
+        config = test_board.config()
+        empty = config['stone']['EMPTY']
+        expect(config['coordinate_system']).to.equal('cartesian_one')

-          invalid = 'invalid'
+        invalid = 'invalid'

-          _row = 2
-          _col = 3
+        _row = 2
+        _col = 3

-          test_board.board.set(invalid, _row, _col)
-          expect(test_board.board.get(_row, _col)).to.equal(invalid)
+        test_board.board.set(invalid, _row, _col)
+        expect(test_board.board.get(_row, _col)).to.equal(invalid)

-          # cartesian_one inverse
-          x = _col + 1
-          y = row - _row
+        # cartesian_one inverse
+        x = _col + 1
+        y = row - _row

-          (-> test_board.get(x, y)).should.throw(Error)
+        (-> test_board.get(x, y)).should.throw(Error)

   describe "when Goban.set(...) is used", ->


Compile subgraphs into parent graphs before executing

--- src/lib/Fbp.coffee
+++ src/lib/Fbp.coffee
@@ -1,9 +1,13 @@
+fs = require 'fs'
+
 class Fbp
     matchPort: new RegExp "([A-Z\.]+)"
-    matchComponent: new RegExp "([A-Za-z]+)\\(([A-Za-z0-9\/\.]+|)\\)"
+    matchComponent: new RegExp "([A-Za-z\.]+)\\(([A-Za-z0-9\/\.]+|)\\)"
+    matchComponentGlobal: new RegExp "([A-Za-z\.]+)\\(([A-Za-z0-9\/\.]+|)\\)", "g"
     matchInitial: new RegExp "\'(.+)\'"
     matchConnection: new RegExp "\-\>"
     matchSeparator: new RegExp "[\\s,\\n]"
+    matchSubgraph: new RegExp "\\'(.+)\\' *-> *GRAPH *([A-Za-z\\.]+)\\(Graph\\)"

     constructor: ->
         @lastElement = null
@@ -14,8 +18,36 @@
         @nodes = {}
         @edges = []

+    loadFile: (file) ->
+        fs.readFileSync file, "utf-8", (err) ->
+            throw err if err
+
+    # Compile subgraphs INTO the parent graph
+    compileSubgraphs: (string) ->
+        loop
+            match = string.match(@matchSubgraph)
+
+            # Done when there's no more subgraphs
+            unless match?
+                return string
+
+            else
+                [match, file, name, index, original] = match
+
+                # Get the FBP of the subgraph and compile that first
+                fbp = @compileSubgraphs(@loadFile(file))
+
+                # Affix the name to the beginning of all components in the subgraph
+                fbp = fbp.replace(@matchComponentGlobal, "#{name}.$1($2)")
+
+                # Replace the graph statement with the FBP
+                string = string.replace(match, fbp)
+
+
     parse: (string) ->
         currentString = ""
+        string = @compileSubgraphs(string)
+
         for char, index in string
             @currentLine++ if char is "\n"


account removal stability

account removal stability

--- server/models/message.coffee
+++ server/models/message.coffee
@@ -197,7 +197,11 @@
         log.info "destroying", rows.length, "messages"

         async.eachLimit rows, CONCURRENT_DESTROY, (row, cb) ->
-            new Message(id: row.id).destroy cb
+            new Message(id: row.id).destroy (err) ->
+                if err?.message is "Document not found"
+                    cb null
+                else
+                    cb err
         , (err) ->

             if err and retries > 0


compatible fix

compatible fix

--- src/lib/bot.coffee
+++ src/lib/bot.coffee
@@ -121,9 +121,9 @@

         bootstrap: (callback) ->

-            app.use require 'compression'
-            app.use require 'method-override'
-            app.use require 'body-parser'
+            app.use require('compression')()
+            app.use require('method-override')()
+            app.use require('body-parser')()

             async.eachSeries serverPluginList, (plugin, callback) ->
                 if plugin.bootstrap?


library: Fix repository lookup from package.json

--- src/library.coffee
+++ src/library.coffee
@@ -83,11 +83,13 @@
 normalizeConfig = (config) ->
   config = {} if not config
   namespace = config.name or null
+  repository = config.repository
   if config.repository?.url
     # package.json convention
-    config.repository = config.repository.url
+    repository = config.repository.url
   config = config.msgflo if config.msgflo # Migth be under a .msgflo key, for instance in package.json

+  config.repository = repository if not config.repository?
   config.namespace = namespace if not config.namespace?
   config.components = {} if not config.components
   config.variables = {} if not config.variables


Oscillator

Oscillator


Use a priority queue to prioritize rendering at coarser resolutions.

--- static/js/app/TileMap.coffee
+++ static/js/app/TileMap.coffee
@@ -270,12 +270,13 @@
         @viewport.zoomIn()
         @clearTiles()
         @render()
+
     zoomOut: () =>
-        if @viewport.get("zoom") > 0
-            @options.beforeZoom?()
-            @viewport.zoomOut()
-            @clearTiles()
-            @render()
+        if not @viewport.get("zoom") > 0 then return
+        @options.beforeZoom?()
+        @viewport.zoomOut()
+        @clearTiles()
+        @render()

     # Get and add the tiles needed to cover the current viewport.
     render: () =>
@@ -291,10 +292,39 @@
 class CanvasTileMap extends TileMap
     required: ["el", "getColor", "bounds"]

+    constructor: () ->
+        super
+        # Enqueue info about needed tiles in arrays, keyed by resolution.
+        @queue = {}
+        @queueLength = 0
+
+    clear: () ->
+        @queue = {}
+        @queueLength = 0
+
+    # Enqueue a tile's info by its resolution.
+    enqueue: (tile) ->
+        resolution = tile.resolution
+        if not @queue[resolution]?
+            @queue[resolution] = []
+        @queue[resolution].push tile
+        @queueLength++
+
+    # Pop the next tile from the queue with coarsest resolution.
+    pop: () ->
+        if @queueLength == 0
+            return null
+
+        resolutions = _.keys(@queue).reverse()
+        for res in resolutions
+            if @queue[res].length > 0
+                next = @queue[res].shift()
+                @queueLength--
+                return next
+
     render: () =>
-        # Use a queue to represent tiles needed (including resolution).
         # Clear the queue on each render.
-        @tileQueue = []
+        @clear()

         zoom = @viewport.get "zoom"
         for [tileX, tileY] in @viewport.getCovering()
@@ -307,22 +337,22 @@
             else
                 resolution = MIN_RESOLUTION

-            # Enqueue this tile/zoom/resolution for rendering.
+            # Enqueue this tile for rendering.
             if resolution >= 1
-                @tileQueue.push
+                @enqueue
+                    resolution: resolution
                     tileX: tileX
                     tileY: tileY
                     zoom: zoom
-                    resolution: resolution

         @renderNextTiles()

-    # Render the next tiles in the queue, pausing for user interaction
-    # after a specified threshold.
+    # Render the next tiles in the queue, prioritizing higher resolutions,
+    # and pausing for user interaction after a specified threshold.
     renderNextTiles: () =>
         start = current = (new Date).getTime()
         while current - start < ANIMATION_SPEED
-            next = @tileQueue.shift()
+            next = @pop()
             if not next?
                 break

@@ -333,11 +363,11 @@
             # Re-enqueue the same tile with a higher resolution.
             if next.resolution > 1
                 next.resolution = next.resolution / 2
-                @tileQueue.push next
+                @enqueue next

             current = (new Date).getTime()

-        if @tileQueue.length > 0
+        if @queueLength > 0
             setTimeout @renderNextTiles, 0

     # Return a canvas tile for the given tile coordinates, zoom,


update HTML page with the necessary hint.

--- cf/spell.coffee
+++ cf/spell.coffee
@@ -12,7 +12,7 @@

   startUp = (data) ->
     $("#question, #answer").show()
-    random_question(data)
+    update_question(data)

   random_question = (data) ->
     choice = Math.floor(Math.random() * data['words'].length)


Generate contacts across world boundaries

--- app/js/world.coffee
+++ app/js/world.coffee
@@ -134,6 +134,56 @@
     super()
     @display.drawBounds()

+  # Returns an array of arrays containing:
+  # [ body A, body B, offset x, offset y ]
+  # where the offset applies to body A for the sake of contact generation.
+  broadPhaseCollisions: ->
+    return [] if @bodies.length < 2
+    pairs = []
+    for i in [0..(@bodies.length-2)]
+      for j in [(i+1)..(@bodies.length-1)]
+        a = @bodies[i]
+        b = @bodies[j]
+
+        # Compare each pair of bodies: if their AABBs overlap each other either
+        # directly or over a wrapped edge, check for contact.
+
+        xOffsets = [0]
+        yOffsets = [0]
+        aBox = a.aabb()
+        bBox = b.aabb()
+
+        xOffsets.push  @sizeX if aBox[0][0] < 0      or bBox[1][0] > @sizeX
+        xOffsets.push -@sizeX if aBox[1][0] > @sizeX or bBox[0][0] < 0
+        yOffsets.push -@sizeY if aBox[1][1] > @sizeY or bBox[0][1] < 0
+        yOffsets.push  @sizeY if aBox[0][1] < 0      or bBox[1][1] > @sizeY
+
+        for x in xOffsets
+          for y in yOffsets
+            if Utils.aabbOverlap a.aabb(), b.aabb(), [x, y]
+              pairs.push [a, b, x, y]
+    pairs
+
+  narrowPhaseCollisions: (pairs) ->
+    contacts = []
+    for [a, b, offsetX, offsetY] in pairs
+      a.position = Vec.add a.position, [offsetX, offsetY]
+      for contact in a.contactPoints b
+        contact.offset = [offsetX, offsetY]
+        contacts.push contact
+      a.position = Vec.sub a.position, [offsetX, offsetY]
+    contacts
+
+  resolveInterpenetration: (contact) ->
+    contact.from.position = Vec.add contact.from.position, contact.offset
+    contact.resolveInterpenetration()
+    contact.from.position = Vec.sub contact.from.position, contact.offset
+
+  resolveVelocity: (contact, dt) ->
+    contact.from.position = Vec.add contact.from.position, contact.offset
+    contact.resolveVelocity dt
+    contact.from.position = Vec.sub contact.from.position, contact.offset
+
   constrainBody: (body) ->
     body.position = @constrainPosition body.position
     body