correct compound strings with embedded quotes

--- grammars/stata.cson
+++ grammars/stata.cson
@@ -70,9 +70,6 @@
     'include': '#string-regular'
   }
   {
-    'include': '#builtin_types'
-  }
-  {
     'include': '#illegal_names'
   }
   {
@@ -189,9 +186,11 @@
       '1':
         'name': 'keyword.functions.data.stata'
     'comment': 'miscellaneous to deal with later',
-    'match': '\\b(tostring|note|label|drop|keep|format)\\b'
+    'match': '\\b(sa(v|ve)|saveold|destring|tostring|note(s)?|format)\\b'
   }
   # Start manual functions:
+  # Prefixes:
+  # # prefix commands: by (bysort) statsby rolling bootstrap jackknife permute simulate svy "mi estimate" nestreg stepwise xi fp mfp capture noisily quietly version
   {
     'match': '\\b(g(e|en|ene|erer|enera|enerat|enerate)?)\\s+((byte|int|long|float|double|str[1-9]?[0-9]?[0-9]?[0-9]?|strL)?\\s+)?([A-Za-z_][A-Za-z0-9_]{0,31})'
     'captures':
@@ -200,6 +199,45 @@
       '3':
         'name': 'support.type.stata'
   }
+  {
+    'match': '\\b(la(b|be|bel)?)\\s+(da(t|ta)?|var(i|ia|iab|iabl|iable)?|de(f|fi|fin|fine)?|val(u|ue|ues)?|di(r)?|l(i|is|ist)?|copy|drop|save|lang(u|ua|uag|uage)?)'
+    'captures':
+      '1':
+        'name': 'keyword.functions.data.stata'
+      '3':
+        'name': 'keyword.functions.data.stata'
+  }
+  {
+    'match':
+      '\\b(drop|keep)\\s+((if)\\s+(.+)\\s+(in)|(in)\\s+(.+)\\s+(if)|(if)|(in))?'
+    'captures':
+      '1':
+        'name': 'keyword.functions.data.stata'
+      '3':
+        'name': 'keyword.functions.data.stata'
+      '4':
+        'patterns': [
+          {
+            'include': '$self'
+          }
+        ]
+      '5':
+        'name': 'keyword.functions.data.stata'
+      '6':
+        'name': 'keyword.functions.data.stata'
+      '7':
+        'patterns': [
+          {
+            'include': '$self'
+          }
+        ]
+      '8':
+        'name': 'keyword.functions.data.stata'
+      '9':
+        'name': 'keyword.functions.data.stata'
+      '10':
+        'name': 'keyword.functions.data.stata'
+  }
 ]
 'repository':
   'builtin_functions':
@@ -282,7 +320,7 @@
   'macro-local':
     'patterns': [
       {
-        'begin': '`'
+        'begin': '`(?!")'
         'beginCaptures':
           '0':
             'name': 'punctuation.definition.string.begin.stata'
@@ -350,8 +388,9 @@
   'operators':
     'patterns': [
       {
-        'match': '\\+|\\-|\\*|\\^'
+        'match': '\\+\\+|\\-\\-|\\+|\\-|\\*|\\^'
         'name': 'keyword.operator.arithmetic.stata'
+        'comment': '++ and -- must come first to support ligatures'
       }
       {
         'comment': 'match division operator but not path separator'
@@ -363,9 +402,12 @@
         'name': 'keyword.operator.logical.stata'
       }
       {
-        'match': '(?:<=|>=|<|>|:=|==|!=|~=|=)'
+        'match': '(?:<=|>=|:=|==|!=|~=|<|>|=|!)'
         'name': 'keyword.operator.comparison.stata'
       }
+        'match': '\\(|\\)'
+        'name': 'keyword.operator.parentheses.stata'
+      }
       {
         'match': '(##|#)'
         'name': 'keyword.operator.factor-variables.stata'
@@ -392,11 +434,16 @@
             'name': 'punctuation.definition.string.end.stata'
         'name': 'string.quoted.double.compound.stata'
         'patterns': [
+          {
+            'match': '"'
+            'name': 'string.quoted.double.compound.stata'
+            'comment': 'This must come before #string-regular and #string-compound to accurately color `"""\' in strings'
+          }
           {
             'include': '#string-regular'
           }
           {
-            'include': '$self'
+            'include': '#string-compound'
           }
           {
             'include': '#macro-local'


Remove unneeded existence checks.

Remove unneeded existence checks.

--- services/web/public/coffee/main/project-list/project-list.coffee
+++ services/web/public/coffee/main/project-list/project-list.coffee
@@ -27,8 +27,8 @@

 		recalculateProjectListHeight = () ->
 			$projListCard = $(".project-list-card")
-			topOffset = $projListCard?.offset()?.top
-			cardPadding = $projListCard?.outerHeight() - $projListCard?.height()
+			topOffset = $projListCard.offset()?.top
+			cardPadding = $projListCard.outerHeight() - $projListCard.height()
 			bottomOffset = $("footer").outerHeight()
 			height = $window.innerHeight - topOffset - bottomOffset - cardPadding
 			$scope.projectListHeight = height


might as well add license header here too

--- resources/assets/coffee/react-namespaces.coffee
+++ resources/assets/coffee/react-namespaces.coffee
@@ -1,3 +1,21 @@
+###
+#    Copyright 2015-2017 ppy Pty. Ltd.
+#
+#    This file is part of osu!web. osu!web is distributed with the hope of
+#    attracting more community contributions to the core ecosystem of osu!.
+#
+#    osu!web is free software: you can redistribute it and/or modify
+#    it under the terms of the Affero GNU General Public License version 3
+#    as published by the Free Software Foundation.
+#
+#    osu!web is distributed WITHOUT ANY WARRANTY; without even the implied
+#    warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+#    See the GNU Affero General Public License for more details.
+#
+#    You should have received a copy of the GNU Affero General Public License
+#    along with osu!web.  If not, see <http://www.gnu.org/licenses/>.
+###
+
 @BeatmapDiscussions = {}
 @Beatmaps = {}
 @BeatmapsetPage = {}


Parse package.json for --version call instead of hard coding it

--- src/main.coffee
+++ src/main.coffee
@@ -11,7 +11,8 @@

 marked.setOptions gfm: on

-if optimist.argv.version? then return console.log "StyleDocco 0.3.5"
+if optimist.argv.version?
+  return console.log "StyleDocco #{require('../package').version}"

 options = optimist
   .usage('Usage: $0 [options] [INPUT]')


Hack to always update from the speakeasy master release

Related to https://github.com/atom/atom-shell/issues/33

--- src/main.coffee
+++ src/main.coffee
@@ -41,7 +41,7 @@
   crashReporter.setAutoSubmit true

 setupAutoUpdater = ->
-  autoUpdater.setAutomaticallyChecksForUpdates false
+  autoUpdater.setFeedUrl 'https://speakeasy.githubapp.com/apps/27/appcast.xml'
   autoUpdater.setAutomaticallyDownloadsUpdates true

 parseCommandLine = ->


checkpoint

checkpoint


Ensure p13n transportation is in a valid state.

Fixes #172.

--- src/p13n.coffee
+++ src/p13n.coffee
@@ -278,6 +278,11 @@
                 return undefined
             return @attributes[attr]

+        _verify_valid_state: ->
+            transport_modes_count = _.filter(@get('transport'), _.identity).length
+            if transport_modes_count == 0
+                @set_transport 'public_transport', true
+
         _fetch: ->
             if not localStorage
                 return
@@ -288,6 +293,7 @@

             stored_attrs = JSON.parse str
             deep_extend @attributes, stored_attrs, ALLOWED_VALUES
+            @_verify_valid_state()

         _save: ->
             if not localStorage


no message

no message

--- src/poll.coffee
+++ src/poll.coffee
@@ -35,7 +35,7 @@
         robot.brain.data.poll[id].option[index].text
         optionsString += "\t#{index}: #{pollOption}\n"
     robot.brain.save()
-    @robot.logg.info "Brain saved"
+    @robot.logger.info "Brain saved"

     text =  """
             POLL ID: #{id}
@@ -48,12 +48,14 @@

 module.exports = (robot) ->
     robot.respond /start poll (.*?)\s(option:\s?.*)+/i, (msg) ->
+        @robot.logger.info "Create poll called"
         topic = msg.match[1]
         options = msg.match[2]
         response = createPoll robot, topic, options
         msg.send "@channel: #{response}"

     robot.respond /poll ping/i, (msg) ->
+        @robot.logger.info "ping called"
         msg.reply "poll pong"


Use file

Use file

--- src/pull.coffee
+++ src/pull.coffee
@@ -5,7 +5,7 @@

 exports.pullImages = (file) ->
   # body...
-  @images = parse.parse('config/images.yml')
+  images = parse.parse(file)
   im = 0
   while im < images.images.length
     exec('docker pull '+images.images[im])


refactor

refactor

--- src/tabs.coffee
+++ src/tabs.coffee
@@ -29,8 +29,8 @@

   connect: (tabName, controller) ->
     @bind 'change', (name) ->
-      controller.active() if name == tabName
-	controller.bind "active", () =>
-	      @change tabName
+      controller.active() if name is tabName
+  	controller.bind 'active', =>
+  	  @change tabName

 module?.exports = Spine.Tabs


Don't add grammars for atom packages until `activate` is called

--- src/app/atom-package.coffee
+++ src/app/atom-package.coffee
@@ -10,6 +10,7 @@
   metadata: null
   keymaps: null
   stylesheets: null
+  grammars: null
   mainModule: null
   deferActivation: false

@@ -53,11 +54,10 @@
       @stylesheets.push([stylesheetPath, loadStylesheet(stylesheetPath)])

   loadGrammars: ->
+    @grammars = []
     grammarsDirPath = fs.join(@path, 'grammars')
     for grammarPath in fs.list(grammarsDirPath, ['.cson', '.json']) ? []
-      grammarContent = fs.readObject(grammarPath)
-      grammar = new TextMateGrammar(grammarContent)
-      syntax.addGrammar(grammar)
+      @grammars.push(TextMateGrammar.loadSync(grammarPath))

   loadScopedProperties: ->
     scopedPropertiessDirPath = fs.join(@path, 'scoped-properties')
@@ -68,6 +68,7 @@
   activate: ->
     keymap.add(map) for map in @keymaps
     applyStylesheet(path, content) for [path, content] in @stylesheets
+    syntax.addGrammar(grammar) for grammar in @grammars

     if @deferActivation
       @subscribeToActivationEvents()


KDFRAMEWORK: fixed missing =

KDFRAMEWORK: fixed missing =

--- client/Framework/components/list/KDListViewController.coffee
+++ client/Framework/components/list/KDListViewController.coffee
@@ -55,7 +55,7 @@

   addDefaultItem:->
     {itemClass,options,data} = @getOptions().defaultItem
-    @getListView().addSubView @defaultItem new itemClass options, data
+    @getListView().addSubView @defaultItem = new itemClass options, data

   removeDefaultItem:->
     @getListView().removeSubView @defaultItem if @defaultItem


Hackathon: added firebase prize, and separated tutum and DO

--- client/landing/site.hackathon/coffee/home/AppViewFull.coffee
+++ client/landing/site.hackathon/coffee/home/AppViewFull.coffee
@@ -474,8 +474,17 @@

-
-
The top winning team will receive $5,000 in account credit from Digital Ocean. and a one year service account credit from Tutum.

+
The top winning team will receive $5,000 in account credit from Digital Ocean.

+

+

+

+

+
Tutum is giving one year service to the winning team.

+

+

+

+

+
Firebase will award $5,500 worth of credit to the winning team.


change Atom config

change Atom config

--- atom.symlink/config.cson
+++ atom.symlink/config.cson
@@ -10,7 +10,9 @@
       "linter-php"
       "linter-rubocop"
       "linter-ruby"
+      "activate-power-mode"
     ]
+    excludeVcsIgnoredPaths: false
     projectHome: "~/Projects"
     themes: [
       "isotope-ui"
@@ -35,6 +37,6 @@
   "release-notes":
     viewedVersion: "0.95.0"
   "tree-view":
-    hideVcsIgnoredFiles: true
+    squashDirectoryNames: true
   welcome:
     showOnStartup: false


fix atom workspace config to be atom 1.0 compliant

atom.workspaceView is no longer available

--- atom.symlink/init.coffee
+++ atom.symlink/init.coffee
@@ -1,6 +1,5 @@
 path = require 'path'

-atom.workspaceView.eachEditorView (editorView) ->
-  editor = editorView.getEditor()
+atom.workspace.observeTextEditors (editor) ->
   if path.extname(editor.getPath()) is '.md'
     editor.setSoftWrap(true)


Read version from file

Read version from file

--- src/tools/upgrade.test.coffee
+++ src/tools/upgrade.test.coffee
@@ -57,5 +57,17 @@
 # Fetch latest version from GitHub & store
 fetch = (arg)->
   return if arg != key()
+  return unless read()
   write latest()
   true
+
+# Read version from file
+read = ->
+  s = ''
+  if fs.FileExists f = path()
+    s = fs.OpenTextFile f
+    .ReadLine()
+  s = s.replace /^\s+/, ''
+  .split /\s+/, 2
+  .shift()
+  filter s unless /^\W/.test s


fix: Make some logging output less chatty by demoting it to 'silly' level

--- src/transaction-runner.coffee
+++ src/transaction-runner.coffee
@@ -69,7 +69,7 @@
       # we need to work with indexes (keys) here, no other way of access.
       async.timesSeries transactions.length, (transactionIndex, iterationCallback) =>
         transaction = transactions[transactionIndex]
-        logger.verbose("Processing transaction ##{transactionIndex}:", transaction.name)
+        logger.verbose("Processing transaction ##{transactionIndex + 1}:", transaction.name)

         logger.verbose('Running \'beforeEach\' hooks.')
         @runHooksForData hooks.beforeEachHooks, transaction, false, =>
@@ -97,7 +97,7 @@
                 @runHooksForData hooks.afterHooks[transaction.name], transaction, false, =>
                   return iterationCallback(@hookHandlerError) if @hookHandlerError

-                  logger.debug("Evaluating results of transaction execution ##{transactionIndex}:", transaction.name)
+                  logger.debug("Evaluating results of transaction execution ##{transactionIndex + 1}:", transaction.name)
                   @emitResult transaction, iterationCallback

       , (iterationError) =>
@@ -518,7 +518,7 @@
       logger.verbose('Handling HTTP response from tested server.')

       res.on 'data', (chunk) ->
-        logger.debug('Recieving some data from tested server.')
+        logger.silly('Recieving some data from tested server.')
         buffer += chunk

       res.on 'error', (error) =>


Fix stray input.

Fix stray input.

--- src/views/search-input.coffee
+++ src/views/search-input.coffee
@@ -45,6 +45,7 @@
             # will only cause the intended typeahead selection.
             'click .tt-suggestion': (e) ->
                 e.stopPropagation()
+            'click input': (e) -> e.stopPropagation()
             'click .typeahead-suggestion.fulltext': 'executeQuery'
             'click .action-button.search-button': 'search'
             'submit .input-container': 'search'


Remove unnecessary check for options.messenger

--- src/coffee/messenger.coffee
+++ src/coffee/messenger.coffee
@@ -408,8 +408,7 @@
             # if options were provided to this individual call, use them
             # don't clobber default options, use _.clone
             sync_msgr_opts = if options.messenger? then _.clone msgr_opts else msgr_opts
-            if options.messenger?
-                _.extend sync_msgr_opts, options.messenger
+            _.extend sync_msgr_opts, options.messenger

             _old_sync.call(Backbone, method, model, options)


add a comparator function

add a comparator function

--- src/util.coffee
+++ src/util.coffee
@@ -13,6 +13,11 @@
   until str.length >= len
     str = fillchar + str
   return str
+
+root.cmp = (a,b) ->
+  return 0  if a == b
+  return -1 if a < b
+  return 1

 # implments x<<n without the braindead javascript << operator
 # (see http://stackoverflow.com/questions/337355/javascript-bitwise-shift-of-long-long-number)


Stop subgraph on shutdown

Stop subgraph on shutdown

--- src/components/Graph.coffee
+++ src/components/Graph.coffee
@@ -113,4 +113,8 @@
   isSubgraph: ->
     true

+  shutdown: ->
+    return unless @network
+    @network.stop()
+
 exports.getComponent = -> new Graph


reassign imports to modules

reassign imports to modules

--- src/crystal/generate.coffee
+++ src/crystal/generate.coffee
@@ -534,6 +534,11 @@
 	# get project
 	project = project or this.project

+	# reassign imports
+	if project.imports
+		project.modules = project.imports
+		delete project.imports
+
 	# load modules
 	if project.modules
 		#console.log "Loading modules for #{project.name}..."


remove dependence on jQuery's extend

remove dependence on jQuery's extend

--- src/dom_text_matcher.coffee
+++ src/dom_text_matcher.coffee
@@ -197,7 +197,10 @@
         analysis.exact or # "Found text matches exactly to pattern"
         (analysis.comparison.errorLevel <= matchThreshold) # still acceptable
       mappings = @mapper.getMappingsForCharRange prefixEnd, suffixStart
-      match = $.extend {}, charRange, analysis, mappings
+      match = {}
+      for obj in [charRange, analysis, mappings]
+        for k, v of obj
+          match[k] = v
       return matches: [match]

 #    console.log "Rejecting the match, because error level is too high. (" +
@@ -253,7 +256,10 @@
         analysis = @analyzeMatch pattern, textMatch, fuzzyComparison
         mappings = @mapper.getMappingsForCharRange textMatch.start,
             textMatch.end
-        match = $.extend {}, textMatch, analysis, mappings
+        match = {}
+        for obj in [charRange, analysis, mappings]
+          for k, v of obj
+            match[k] = v
         matches.push match
         null
     t3 = @timestamp()


Destroy the editor when an EditorComponent is unmounted

Fixes #3222

--- src/editor-component.coffee
+++ src/editor-component.coffee
@@ -189,6 +189,7 @@
     window.removeEventListener 'resize', @requestHeightAndWidthMeasurement
     clearInterval(@domPollingIntervalId)
     @domPollingIntervalId = null
+    @props.editor.destroy()

   componentWillUpdate: ->
     wasVisible = @visible


update with `$wrapsignals()` demo

update with `$wrapsignals()` demo

--- src/experiments/demo.coffee
+++ src/experiments/demo.coffee
@@ -15,33 +15,79 @@
 help                      = CND.get_logger 'help',      badge
 urge                      = CND.get_logger 'urge',      badge
 echo                      = CND.echo.bind CND
-PS 												= require '../..'
-{ $ }											= PS
+PS                        = require '../..'
+{ $
+  $watch }                = PS
+first                     = Symbol 'first'
+last                      = Symbol 'last'
+first                     = Symbol 'first'
+last                      = Symbol 'last'
+between                   = Symbol 'between'
+after                     = Symbol 'after'
+before                    = Symbol 'before'

 #-----------------------------------------------------------------------------------------------------------
-@demo = -> new Promise ( resolve, reject ) =>
-	source 		= PS.new_push_source()
-	pipeline 	= []
-	pipeline.push source
-	pipeline.push PS.$show()
-	pipeline.push PS.$drain =>
-		help 'ok'
-		resolve()
-	PS.pull pipeline...
-	for idx in [ 1 .. 5 ]
-		source.send idx
-	source.end()
-	return null
+@$show_signals = -> $ { first, last, between, after, before, }, ( d, send ) =>
+  info d
+  send d
+
+#-----------------------------------------------------------------------------------------------------------
+@demo_signals = -> new Promise ( resolve, reject ) =>
+  source    = PS.new_push_source()
+  pipeline  = []
+  pipeline.push source
+  pipeline.push @$show()
+  pipeline.push PS.$drain resolve
+  PS.pull pipeline...
+  for idx in [ 1 .. 5 ]
+    source.send idx
+  source.end()
+  return null
+
+#-----------------------------------------------------------------------------------------------------------
+@$wrapsignals = ->
+  ### NOTE: this functionality has been implemented in PipeDreams ###
+  is_first  = true
+  prv_d     = null
+  return $ { last, }, ( d, send ) =>
+    if d is last
+      if prv_d?
+        prv_d.first = true if is_first
+        prv_d.last  = true
+        send prv_d
+    else
+      if prv_d?
+        send prv_d
+      prv_d       = { value: d, }
+      prv_d.first = true if is_first
+      is_first    = false
+    return null
+
+#-----------------------------------------------------------------------------------------------------------
+@demo_wrapsignals = -> new Promise ( resolve, reject ) =>
+  source    = PS.new_push_source()
+  pipeline  = []
+  pipeline.push source
+  pipeline.push PS.$wrapsignals()
+  pipeline.push PS.$show()
+  pipeline.push PS.$drain resolve
+  PS.pull pipeline...
+  n = 5
+  for idx in [ 1 .. n ]
+    source.send idx
+  source.end()
+  return null

 ############################################################################################################
 unless module.parent?
-	L = @
-	do ->
-		await L.demo()
+  L = @
+  do ->
+    # await L.demo_signals()
+    await L.demo_wrapsignals()
+    help 'ok'

-


Tweak gun gen params

Tweak gun gen params

--- src/generation/items.coffee
+++ src/generation/items.coffee
@@ -25,7 +25,7 @@

 		when 'sniper'
 			gun.range = game.random.range 18, 26
-			gun.damage = game.random.range 12, 15
+			gun.damage = game.random.range 17, 20
 			gun.accuracy = game.random.rangeFloat 0.9, 0.99

 		when 'shotgun'


callback -> promise

callback -> promise

--- src/hubot/reputation.coffee
+++ src/hubot/reputation.coffee
@@ -11,8 +11,8 @@

   robot.respond /show reputation of (.+)$/i, (msg) ->
     identity = msg.match[1]
-    Reputation.report identity, (report) ->
-      msg.send report
+    Reputation.report identity
+      .then (report) => msg.send report

   robot.respond /rate (.+) ([\d.]+)% (?:at |on )(.+?)$/i, (msg) ->
     msg.match.shift()


operations: fix ../util require, CreateNode safety check

--- src/local/operations.coffee
+++ src/local/operations.coffee
@@ -1,5 +1,6 @@
 logger = require('../logger').makeLogger 'local/operations'
-{inspect, getNodeUser} = require('util')
+{inspect} = require('util')
+{getNodeUser} = require('../util')
 async = require('async')
 uuid = require('node-uuid')
 errors = require('../errors')
@@ -339,7 +340,11 @@

 class CreateNode extends ModelOperation
     transaction: (t, cb) ->
-        nodePrefix = "/user/#{getNodeUser @req.node}/"
+        nodeUser = getNodeUser @req.node
+        unless nodeUser
+            return cb new Error("Malformed node")
+
+        nodePrefix = "/user/#{nodeUser}/"
         async.waterfall [(cb2) =>
             t.getNodeOwnersByPrefix nodePrefix, cb2
         , (owners, cb2) =>


embed fixes and buttons back to not buttons

--- static/embed/oab_embed.coffee
+++ static/embed/oab_embed.coffee
@@ -210,7 +210,6 @@
   this._loading = false # tracks when loads are occurring
   this.submit_after_metadata = false # used by instantill to track if metadata has been provided by user
   this.file = false # used by syp to store the file for sending to backend
-  this.first = true # used below in find to track first user interaction - may be obsoleted or better replaced with alternative approach though

   _L.loaded = this.loaded if this.loaded? # if this is set to a function, it will be passed to _leviathan loaded, which gets run after every ajax call completes

@@ -249,6 +248,7 @@
   if window.location.search.indexOf('section=') isnt -1
     this.section window.location.search.split('section=')[1].split('&')[0].split('#')[0]
   this.find() if this.data.doi or (this.plugin is 'instantill' and (this.data.title or this.data.url))
+  window.addEventListener "popstate", (pe) => this.state(pe)
   return this

@@ -288,19 +288,20 @@
 _oab.prototype.state = (pop) ->
   if this.pushstate
     try
-      if pop?
-        # what to do with the pop event? for now just triggers a restart if uses tries to go back
-        this.restart()
-      else
+      u = window.location.pathname
+      if not pop?
         if window.location.href.indexOf('shareyourpaper.org') isnt -1 and this.data.doi?
           u = window.location.pathname.split('/10.')[0] + '/' + this.data.doi + window.location.search + window.location.hash
-        else if (this.data.doi? or this.data.title? or this.data.url?) and window.location.href.indexOf('/setup') is -1 and window.location.href.indexOf('/demo') is -1
-          k = if this.data.doi then 'doi' else if this.data.title then 'title' else 'url'
-          u = window.location.pathname
-          u += window.location.search.split('?' + k + '=')[0].split('&' + k + '=')[0]
-          u += if u.indexOf('?') is -1 then '?' else '&'
-          u += k + '=' + this.data[k] + window.location.hash
-        window.history.pushState "", "find", u
+        else if window.location.href.indexOf('/setup') is -1 and window.location.href.indexOf('/demo') is -1
+          if this.data.doi? or this.data.title? or this.data.url?
+            k = if this.data.doi then 'doi' else if this.data.title then 'title' else 'url'
+            u += window.location.search.split('?' + k + '=')[0].split('&' + k + '=')[0]
+            u += if u.indexOf('?') is -1 then '?' else '&'
+            u += k + '=' + this.data[k] + window.location.hash
+      window.history.pushState "", (if pop? then "search" else "find"), u
+      if pop?
+        # what to do with the pop event? for now just triggers a restart if user tries to go back
+        this.restart()

 _oab.prototype.restart = (e, val) ->
   try e.preventDefault()
@@ -313,8 +314,6 @@
   this.configure()
   if val
     _L.set '#_oab_input', val
-    if this.pushstate and 'pushState' in window.history and window.location.href.indexOf(val) isnt -1
-      window.history.pushState "", "find", (if window.location.href.indexOf('/' + val) isnt -1 then window.location.pathname.replace('/' + val,'') + window.location.search + window.location.hash else window.location.pathname + window.location.search.replace('doi='+input,'') + window.location.hash)
     this.find()
   else
     _L.set '#_oab_input', ''
@@ -478,15 +477,14 @@
   try e.preventDefault()
   if not this.data.email and _L.gebi '#_oab_email'
     this.validate()
-  else
-    if not this.f?.url? and fl = _L.gebi '_oab_file'
-      if fl.files? and fl.files.length
-        this.file = new FormData()
-        this.file.append 'file', fl.files[0]
-      else
-        _L.show '#_oab_error', '
Whoops, you need to give us a file! Check it\'s uploaded.
'
-        _L.css fl, 'border-color','#f04717'
-        return
+  else if fl = _L.gebi '_oab_file'
+    if fl.files? and fl.files.length
+      this.file = new FormData()
+      this.file.append 'file', fl.files[0]
+    else if not this.f?.url? and not this.f?.permissions?.permissions?.archiving_allowed and not this.config.dark_deposit_off
+      _L.show '#_oab_error', '
Whoops, you need to give us a file! Check it\'s uploaded.
'
+      _L.css fl, 'border-color','#f04717'
+      return

     this.loading()
     # this could be just an email for a dark deposit, or a file for actual deposit
@@ -549,6 +547,7 @@
   this.f = data if data?
   this.loading false
   if this.f?.doi_not_in_crossref
+    this.f = {}
     _L.show '#_oab_error', '
Double check your DOI, that doesn\'t look right to us.
'
     _L.gebi('_oab_input').focus()
   else if this.f?.metadata?.crossref_type? and this.f.metadata.crossref_type not in ['journal-article', 'proceedings-article']
@@ -557,7 +556,7 @@
     if this.cml()
       nj += ' To get help with depositing, click here'
+      nj += "?subject=Help%20depositing%20&body=Hi%2C%0D%0A%0D%0AI'd%20like%20to%20deposit%3A%0D%0A%0D%0A%3C%3CPlease%20insert%20a%20full%20citation%3E%3E%0D%0A%0D%0ACan%20you%20please%20assist%20me%3F%0D%0A%0D%0AYours%20sincerely%2C" + '">click here'
     _L.show '#_oab_error', nj + '.
'
   else if not this.f?.metadata?.title?
     _L.show '#_oab_error', '
Unknown paper

Sorry, we cannot find this paper or sufficient metadata. ' + this.contact() + '
'
@@ -597,6 +596,7 @@
       # it is already OA, depending on settings can deposit another copy
       _L.set '._oab_oa_url', 'href', this.f.url
       if this.config.allow_oa_deposit # when true, they can't...
+        _L.hide '._oab_get_email'
         _L.show '._oab_oa'
       else
         _L.show '._oab_oa_deposit'
@@ -619,6 +619,7 @@
         pm += encodeURIComponent 'To whom it may concern,\n\nAttached is written confirmation of permission I\'ve been given to deposit, and the permitted version of my paper: '
         pm += encodeURIComponent '"' + (this.f.metadata?.title ? this.f.metadata?.doi ? 'Untitled paper') + '" \n\nCan you please deposit it into the repository on my behalf? \n\nSincerely, '
         _L.set '#_oab_permissionemail', 'href', pm
+        _L.hide '._oab_get_email'
         _L.show '._oab_permission_required'
       else
         # can be shared, depending on permissions info
@@ -630,6 +631,7 @@
         _L.show '._oab_archivable'
     else
       # can't be directly shared but can be passed to library for dark deposit
+      _L.hide '#_oab_file'
       _L.show '._oab_dark_deposit'

 _oab.prototype.findings = (data) -> # only used by instantill
@@ -714,11 +716,7 @@
     this.metadata()

 _oab.prototype.find = (e) ->
-  try
-    e.preventDefault()
-    if this.first
-      this.first = false # or should just bind this from the start? previously was not bound because waits for first user interaction...
-      window.addEventListener "popstate", (pe) -> this.state(pe)
+  try e.preventDefault()
   if JSON.stringify(this.f) isnt '{}'
     for k in ['title','journal','year','doi']
       if v = _L.get '#_oab_' + k
@@ -767,7 +765,7 @@
       delete this.data.id
     _L.show '#_oab_error', '
❌ Sorry please provide ' + if this.plugin is 'instantill' then 'the full DOI, title, citation, PMID or PMC ID.
' else 'a valid DOI.
'
   else
-    this.state() # should this just be part of loading?
+    this.state()
     this.loading()
     this.data.config = this.config
     this.data.from ?= this.uid
@@ -844,7 +842,7 @@

A title

And citation string, OR demo title OR Unknown article and refer to library

-
This is not the article I searched

+
This is not the article I searched

We have an online copy instantly available

You should be able to access it on the publisher\'s website.

@@ -901,7 +899,7 @@

We\'ll gather information about your paper and find the easiest way to share it.

<input class="_oab_form" type="text" id="_oab_input" placeholder="e.g. 10.1126/scitranslmed.3001922" aria-label="Enter a search term" style="box-shadow:none;"></input>

Next

-
My paper doesn\'t have a DOI

+
My paper doesn\'t have a DOI

@@ -909,15 +907,23 @@

Your paper is already freely available!

Great news, you\'re already getting the benefits of sharing your work! Your publisher or co-author have already shared it.

See free version

-
Do another

-

+
Do another

+

+
+

+
You may share your paper if you ask the journal

+
Unlike most, the journal requires that you ask them before you share your paper freely.
+    Asking only takes a moment as we find out who to contact and have drafted an email for you.

+
Review Email

+
I\'ve got permission now!

+

Your paper is already freely available!

Great news, you\'re already getting the benefits of sharing your work! Your publisher or co-author have already shared a freely available copy.

Give us your email to confirm deposit

-
+

You can freely share your paper now!

The library has checked and the journal encourages you to freely share your paper so colleagues and the public can freely read and cite it.

@@ -930,14 +936,6 @@

✓ Tell us your email

-

-
You may share your paper if you ask the journal

-
Unlike most, the journal requires that you ask them before you share your paper freely.
-    Asking only takes a moment as we find out who to contact and have drafted an email for you.

-
Review Email

-
I\'ve got permission now!

-

-

You can share your paper!

We checked and unfortunately the journal won\'t let you share your paper freely with everyone.

@@ -969,7 +967,7 @@

You\'re nearly done. It looks like what you uploaded is a publisher\'s PDF which your journal prohibits legally sharing.

     We just need the version accepted by the journal to make your work available to everyone.

Try uploading again

-
My upload was an accepted manuscript

+
My upload was an accepted manuscript

@@ -1004,7 +1002,7 @@

All that\'s left to do is wait. Once the journal gives you permission to share, come back and we\'ll help you finish the job.

-
Do another

+
Do another

'
@@ -1068,11 +1066,11 @@
         el.innerHTML = (if cs is 'a paper' then 'an article' else if cs is 'paper' then 'article' else 'Article')
     if this.config.pilot
       pilot = '

Notice a change? We\'re testing a simpler and faster way to ' + (if this.plugin is 'instantill' then 'get' else 'deposit') + ' your ' + (if this.config.saypaper then 'paper' else 'article') + (if this.plugin is 'instantill' then '' else 's') + '. You can '
-      pilot += 'give feedback or '
+      pilot += 'give feedback or '
       if this.plugin is 'instantill'
         pilot += 'use the old form.
'
       else
-        pilot += 'use the old way.
'
+        pilot += 'use the old way.
'
       _L.html '#_oab_pilot', pilot
     else
       _L.html '#_oab_pilot', ''
@@ -1088,8 +1086,8 @@
     else if this.plugin is 'instantill'
       if this.config.book or this.config.other
         boro = '
Need '
-        boro += 'a book chapter' if this.config.book
-        boro +=  (if this.config.book then ' or ' else ' ') + 'something else' if this.config.other
+        boro += 'a book chapter' if this.config.book
+        boro +=  (if this.config.book then ' or ' else ' ') + 'something else' if this.config.other
         _L.html '#_oab_book_or_other', boro + '?
'
       else
         _L.html '#_oab_book_or_other', ''


Set data for test

Set data for test

--- spec/lanes/components/grid/RowEditorSpec.coffee
+++ spec/lanes/components/grid/RowEditorSpec.coffee
@@ -20,7 +20,9 @@
     })

 RenderEdit = (q, value) ->
+    Lanes.Test.syncSucceedWith(ROW_DATA)
     grid = RenderGrid(q)
+
     new _.Promise (res, rej) -> _.defer ->
         _.dom(grid).qs(LAST_ROW_SELECTOR).click()
         editor = Lanes.Test.Utils.findRenderedComponentWithType(


New method for dispatching events (solving issue 1)

--- vendor/assets/javascripts/twitter/bootstrap/rails/confirm.coffee
+++ vendor/assets/javascripts/twitter/bootstrap/rails/confirm.coffee
@@ -42,7 +42,31 @@

           $.rails.allowAction = ->
             true
-          element.trigger "click"
+
+          if element.get(0).click
+            element.get(0).click()
+          else if $.isFunction(document.createEvent)
+            evt = document.createEvent "MouseEvents"
+            evt.initMouseEvent(
+              "click",
+              true,   # e.bubbles,
+              true,   #e.cancelable,
+              window, #e.view,
+              0,      #e.detail,
+              0,      #e.screenX,
+              0,      #e.screenY,
+              0,      #e.clientX,
+              0,      #e.clientY,
+              false,  #e.ctrlKey,
+              false,  #e.altKey,
+              false,  #e.shiftKey,
+              false,  #e.metaKey,
+              0,      #e.button,
+              document.body.parentNode #e.relatedTarget
+            )
+            element.get(0).dispatchEvent(evt)
+          else
+            element.trigger "click"

           $.rails.allowAction = allowAction


Fix documentation typo

Fix documentation typo

--- _source/server/lib/_entityHelpers.coffee
+++ _source/server/lib/_entityHelpers.coffee
@@ -16,7 +16,7 @@

   ###

-    Apollos.documentHelpers.updaten
+    Apollos.documentHelpers.update

     @example update a doc in apollos with data from a platform


Fixed main modal view scroll when comming from a infoDialog (#781)

* Fixed main modal view scroll when comming from a infoDialog

* typos

--- app/assets/javascripts/dialogs.js.coffee
+++ app/assets/javascripts/dialogs.js.coffee
@@ -72,6 +72,14 @@
     $html.on 'hidden.bs.modal', (e) ->
       # remove confirming mark
       link.removeAttr('data-confirming')
+
+      # https://github.com/twbs/bootstrap/issues/15260
+      # Closing the confirm dialog when the main modal view still open removes the class "modal-open" from body which prevents the modal view to be scrolled again
+      # check if the main modal view still open
+      if $('#mainModal.modal.in').length > 0
+        # set back the class "modal-open" back to the body so the main modal view can still be used
+        $("body").addClass("modal-open");
+

     $html.modal()


cleanup flexirails more

cleanup flexirails more

--- app/assets/javascripts/flexirails.coffee
+++ app/assets/javascripts/flexirails.coffee
@@ -153,6 +153,7 @@
       @loadedRows += arr.length
       cur_req = Math.round(@loadedRows / @_defaults.limitFetchResultsTo)

+      tbody = document.createElement('tbody')
       if (arr.length is 0)
         _tr = document.createElement('tr')
         _tr.className = 'no_results'
@@ -163,10 +164,11 @@
         td.appendChild(document.createTextNode(@t('no_results')))

         _tr.appendChild(td)
-        fragment.appendChild(_tr)
-
-      fragment.appendChild(@buildFlexiRow(item)) for item in arr
-
+        tbody.appendChild(_tr)
+      else
+        tbody.appendChild(@buildFlexiRow(item)) for item in arr
+
+      fragment.appendChild tbody
       @flexiTable.appendChild(fragment.cloneNode(true))

       @setupFirstLastColumns()


Clean up the Git protocol switcher JS

Clean up the Git protocol switcher JS

Also re-adds the `.clone` updating that was removed accidentally.
Thanks, tests!

--- app/assets/javascripts/project.js.coffee
+++ app/assets/javascripts/project.js.coffee
@@ -1,11 +1,19 @@
 class @Project
   constructor: ->
-    # Git clone panel switcher
-    cloneHolder = $('.git-clone-holder')
-    if cloneHolder.length
-      $('.js-protocol-switch', cloneHolder).click ->
-        $('.js-protocol-switch', cloneHolder).toggleClass('active')
-        $('#project_clone').val($(@).data('clone'))
+    # Git protocol switcher
+    $('.js-protocol-switch').click ->
+      return if $(@).hasClass('active')
+
+      # Toggle 'active' for both buttons
+      $('.js-protocol-switch').toggleClass('active')
+
+      url = $(@).data('clone')
+
+      # Update the input field
+      $('#project_clone').val(url)
+
+      # Update the command line instructions
+      $('.clone').text(url)

     # Ref switcher
     $('.project-refs-select').on 'change', ->


Update report on window resize.

Update report on window resize.

--- app/assets/javascripts/reports.js.coffee
+++ app/assets/javascripts/reports.js.coffee
@@ -199,6 +199,7 @@
         initialize: (options) ->
                 @api = options.api
                 @listenTo(@api, "navigate", @navigate)
+                $(window).resize(@resize)

         navigate: (old_state,new_state,changed) ->
                 console.log("rep navigate")
@@ -221,6 +222,8 @@
                 else
                         @submissionDetailsView.updateView() if @submissionDetailsView?

+        resize: () =>
+                @reportView.updateView() if @reportView?

 class ListFolderController extends Marionette.Controller


Datepicker funcional

Datepicker funcional

--- app/assets/javascripts/surveys.js.coffee
+++ app/assets/javascripts/surveys.js.coffee
@@ -2,5 +2,4 @@
 # All this logic will automatically be available in application.js.
 # You can use CoffeeScript in this file: http://coffeescript.org/

-$('#survey_deadline').click ->
-  alert "Click"
+$('#survey_deadline').datepicker()


Increase zoom level.

Increase zoom level.

--- app/assets/javascripts/tracker.js.coffee
+++ app/assets/javascripts/tracker.js.coffee
@@ -48,7 +48,7 @@
 options =
   projection: 'EPSG:900913'
   units: 'm'
-  numZoomLevels: 16
+  numZoomLevels: 17
   maxResolution: 156543.0339
   maxExtent: new (OpenLayers.Bounds)(-20037508.34, -20037508.34, 20037508.34, 20037508.34)
   controls: [new OpenLayers.Control.PanZoomBar,


rename functions

rename functions

--- app/assets/javascripts/zvg/column.coffee
+++ app/assets/javascripts/zvg/column.coffee
@@ -14,18 +14,26 @@
   #

-  # reset s1 label container height
   # rotate labels, or stagger?
   # reset s1 label text-anchor
-  constructor: (element) ->
+  # line breaks in s1 labels?
+  #
+  constructor: (element, options = {}) ->
     super(element)
+    @_options = options
     @initializeSeries1LabelContainer()

   randomizeData: (s1count, s2count, s3count) ->
+    @_filters = null
     @undimAllValues()
+    randomness = ->
+      i = parseInt(Math.random() * 25)
+      'Hello This is Extra Text'.substr(0, i)
     s1count or= parseInt(Math.random() * 10 + 1)
     s2count or= parseInt(Math.random() * 6 + 1)
     s3count or= parseInt(Math.random() * 8 + 2)
+
+    s1d = ("Survey#{randomness()}#{n}" for n in [1..s1count])

     raw = []
     for s in ([1..s1count])
@@ -36,12 +44,12 @@
             for d in ([1..s3count])
               do (d) ->
                 raw.push {
-                  series_1: "Survey #{s}"
+                  series_1: s1d[s-1]
                   series_2: "Filter #{f}"
                   series_3: d
                   value: parseInt(Math.random() * 150)
                 }
-    @series_1_domain("Survey #{n}" for n in [1..s1count])
+    @series_1_domain("Survey#{randomness()}#{n}" for n in [1..s1count])
     @series_2_domain("Filter #{n}" for n in [1..s2count])
     @series_3_domain("#{n}" for n in [1..s3count])
     @data(raw)
@@ -67,8 +75,8 @@
     @renderSeries1()
     @buildSeriesDomains()
     @renderSeries2()
-    @appendSeries1Labels()
-    @appendSeries2Labels()
+    @renderSeries1Labels()
+    @renderSeries2Labels()
     @initializeY()
     @initializeLabels()
     #@appendSeries2Borders()
@@ -331,9 +339,8 @@

   initializeSeries1LabelContainer: ->
     @series_1_label_container = @svg.append('g')
-      .attr('transform', "translate(0, #{@height + 30})")
-
-  appendSeries1Labels: ->
+
+  renderSeries1Labels: ->
     @series_1_labels = @series_1_label_container.selectAll('text.series1label')
       .data(@_data)
     @series_1_labels.enter()
@@ -347,7 +354,7 @@
     @constructSeries1LabelMap()
     @rotateSeries1Labels() if @detect_overlaps(@series1LabelMap)

-  appendSeries2Labels: (rotate = 0) ->
+  renderSeries2Labels: (rotate = 0) ->
     @svg.selectAll('.series2label').remove()
     @series_2_labels = @series_1.selectAll('text.series2label')
       .data((d) -> d.values)
@@ -362,9 +369,8 @@
         "rotate(#{rotate}, #{x}, #{y})"
       ).style("text-anchor", if rotate is 0 then 'middle' else 'end')
       .text((d) =>
-        console.log(d.values)
         sum = d3.sum(value.values[0].value for value in d.values)
-        "#{d.key} (#{sum})"
+        "#{@series_2_label_visibility(d.key)} (#{sum})"
       )

     @series_2_labels.on('click', (d,i) =>
@@ -372,16 +378,17 @@
       @render()
     )
     @constructSeries2LabelMap()
-    @rotateSeries2Labels() if @detect_overlaps(@series2LabelMap) and (rotate is 0)
+    if @detect_overlaps(@series2LabelMap) and (rotate is 0)
+      @rotateSeries2Labels()
+    else
+      @series_1_label_container.transition().attr('transform', "translate(0, #{@height + 30})")

   rotateSeries2Labels: ->
-    console.log("ROTATING LABELS")
     max_length = d3.max(l.length for l in @series2LabelMap)
-    @appendSeries2Labels(-90)
+    @renderSeries2Labels(-90)
     @series_1_label_container.transition().attr('transform', "translate(0, #{@height + max_length + 25})")

   rotateSeries1Labels: ->
-    console.log("ROTATING LABELS (1)")
     @series_1_labels.attr('transform', (d) ->
       s = d3.select(@)
       x = s.attr('x')
@@ -389,7 +396,11 @@
       "rotate(-45, #{x}, #{y})"
     ).style('text-anchor', 'end')

-
+  series_2_label_visibility: (label) ->
+    if @series_2_domain()[0] is 'all' and @series_2_domain().length is 1
+      ""
+    else
+      label

   staggerSeries2Labels: ->
     @series_1_label_container.transition().attr('transform', "translate(0, #{@height + 45})")
@@ -400,7 +411,6 @@
           d3.select(label).attr('y', 15 + current_y)

   staggerSeries1Labels: ->
-    console.log("STAGGERING LABELS")
     for label, index in @series_1_labels[0]
       do (label, index) =>
         if (index % 2) is 1


add staging creds to secrets

add staging creds to secrets

--- app/templates/gulp/secrets-sample.coffee
+++ app/templates/gulp/secrets-sample.coffee
@@ -9,6 +9,12 @@
 		pass: "db_password"
 		host: "db_host"

+	db_staging:
+		name: "db_name"
+		user: "db_username"
+		pass: "db_password"
+		host: "db_host"
+
 	db_dev:
 		name: "db_name"
 		user: "db_username"


Don't adjust match index when no matches exist

--- src/extensions/autocomplete/autocomplete.coffee
+++ src/extensions/autocomplete/autocomplete.coffee
@@ -135,11 +135,13 @@
       @css(left: left, top: potentialTop, bottom: 'inherit')

   selectPreviousMatch: ->
+    return if @filteredMatches.length is 0
     previousIndex = @currentMatchIndex - 1
     previousIndex = @filteredMatches.length - 1 if previousIndex < 0
     @selectMatchAtIndex(previousIndex)

   selectNextMatch: ->
+    return if @filteredMatches.length is 0
     nextIndex = (@currentMatchIndex + 1) % @filteredMatches.length
     @selectMatchAtIndex(nextIndex)


rsm_walk: fix serious errors

rsm_walk: fix serious errors

--- src/rsm_walk.coffee
+++ src/rsm_walk.coffee
@@ -11,11 +11,11 @@

             if lastOffset
                 # Remote supports RSM, walk:
-                if seenOffsets.hasOwnProperty(nextOffset)
+                if seenOffsets.hasOwnProperty(lastOffset)
                     cb new Error("RSM offset loop detected for #{@request.node}: #{offset} already seen")
                 else
-                    seenOffsets[nextOffset] = true
-                    walk nextOffset
+                    seenOffsets[lastOffset] = true
+                    walk lastOffset
             else
                 # No RSM support, done:
                 cb()


fix connect when serve

fix connect when serve

--- src/steroids.coffee
+++ src/steroids.coffee
@@ -291,51 +291,53 @@
                 watcher.watch("./config")

-              server = Server.start callback: ()=>
-                global.steroidsCli.server = server
-
-                buildServer = new BuildServer
-                                    path: "/"
-                                    port: @port
-
-                server.mount(buildServer)
-
-                unless argv.qrcode?
-                  QRCode = require "./steroids/QRCode"
-                  QRCode.showLocal
-                    port: @port
-
-                  util.log "Waiting for the client to connect, scan the QR code visible in your browser ..."
-
-                setInterval () ->
-                  activeClients = 0;
-                  needsRefresh = false
-
-                  for ip, client of buildServer.clients
-                    delta = Date.now() - client.lastSeen
-
-                    if (delta > 2000)
-                      needsRefresh = true
-                      delete buildServer.clients[ip]
-                      console.log ""
-                      util.log "Client disconnected: #{client.ipAddress} - #{client.userAgent}"
-                    else if client.new
-                      needsRefresh = true
-                      activeClients++
-                      client.new = false
-
-                      console.log ""
-                      util.log "New client: #{client.ipAddress} - #{client.userAgent}"
-                    else
-                      activeClients++
-
-                  if needsRefresh
-                    util.log "Number of clients connected: #{activeClients}"
-                    prompt.refresh()
-
-                , 1000
-
-                prompt.connectLoop()
+              server = Server.start
+                port: @port
+                callback: ()=>
+                  global.steroidsCli.server = server
+
+                  buildServer = new BuildServer
+                                      path: "/"
+                                      port: @port
+
+                  server.mount(buildServer)
+
+                  unless argv.qrcode?
+                    QRCode = require "./steroids/QRCode"
+                    QRCode.showLocal
+                      port: @port
+
+                    util.log "Waiting for the client to connect, scan the QR code visible in your browser ..."
+
+                  setInterval () ->
+                    activeClients = 0;
+                    needsRefresh = false
+
+                    for ip, client of buildServer.clients
+                      delta = Date.now() - client.lastSeen
+
+                      if (delta > 2000)
+                        needsRefresh = true
+                        delete buildServer.clients[ip]
+                        console.log ""
+                        util.log "Client disconnected: #{client.ipAddress} - #{client.userAgent}"
+                      else if client.new
+                        needsRefresh = true
+                        activeClients++
+                        client.new = false
+
+                        console.log ""
+                        util.log "New client: #{client.ipAddress} - #{client.userAgent}"
+                      else
+                        activeClients++
+
+                    if needsRefresh
+                      util.log "Number of clients connected: #{activeClients}"
+                      prompt.refresh()
+
+                  , 1000
+
+                  prompt.connectLoop()


store unique seed in cookie and generate test options from seed

--- js/libs/circle/ab-tests.coffee
+++ js/libs/circle/ab-tests.coffee
@@ -1,19 +1,16 @@
 # Caveats:
-#   Don't set null as an option.
 #   You can't change the options once they're set, you need to define a new test
 #
 # Example setup in viewModel:
 #   @ab = new(ABTests({daniel_test: ["option1", "option2"]})).ab_tests
 #
 # Example usage in view:
-#   %p{href: "#", data-bind: "if: AB.daniel_test == true"}
+#   %p{href: "#", data-bind: "if: ab().daniel_test == true"}
 #     This is the text that will show up if option is set to true
-#   %p{href: "#", data-bind: "if: AB.daniel_test == false"}
+#   %p{href: "#", data-bind: "if: ab().daniel_test == false"}
 #     This is the text that will show up if option is set to false

 exports = this
-
-$.cookie.json = true

 randInt = (n) ->
   Math.floor(Math.random() * n)
@@ -22,7 +19,9 @@
   constructor: (test_definitions, options) ->
     options or= {}

-    @cookie_prefix = options.cookie_prefix || "ab_test_"
+    @cookie_name = options.cookie_name || "ab_test_user_seed"
+
+    @user_seed = @get_user_seed()

     # defs don't need to be an observable, but we may want to do
     # inference in the future. Better to set it up now.
@@ -34,32 +33,23 @@

     @setup_tests()

-  set_option: (test_name, value) =>
-    tests = @ab_tests()
-    tests[test_name] = value
+  get_user_seed: =>
+    if not $.cookie(@cookie_name)?
+      $.cookie(@cookie_name, Math.random(), {expires: 365, path: "/"})
+    parseFloat($.cookie(@cookie_name))
+
+  option_index: (seed, name, options) =>
+    Math.abs(CryptoJS.MD5("#{seed}#{name}").words[0] % options.length)
+
+  setup_tests: () =>
+    tests = {}
+    for name, options of @test_definitions()
+
+      value = options[@option_index(@user_seed, name, options)]
+
+      tests[name] = value
+
+      console.log "Set (or reseting) A/B test '#{name}' to value '#{value}'"

     @ab_tests(tests)
-
-    $.cookie @cookie_prefix + test_name, value,
-      expires: 365
-      path: "/"
-
-    kmq_choice = {}
-    kmq_choice[test_name] = value
-
-    _kmq.push ["set", kmq_choice]
-
-    console.log "Set (or reseting) A/B test '#{test_name}' to value '#{value}'"
-
-  setup_tests: () =>
-    for test_name, options of @test_definitions()
-      if $.cookie(@cookie_prefix + test_name) is null
-        @choose_and_set_option(test_name)
-      else
-        console.log "Found A/B test value for '#{test_name}'"
-        @set_option(test_name, $.cookie(@cookie_prefix + test_name))
-
-  choose_and_set_option: (test_name) =>
-    options = @test_definitions()[test_name]
-    i = randInt(options.length)
-    @set_option(test_name, options[i])
+    _kmq.push ["set", tests]


wait until step is complete to advance

wait until step is complete to advance

--- tutor/src/components/task/progress/panel.cjsx
+++ tutor/src/components/task/progress/panel.cjsx
@@ -46,8 +46,8 @@
         @props.goToStep(@props.stepKey + 1)
       )
       TaskActions.completeStep(stepId)
-
-    @props.goToStep(@props.stepKey + 1)
+    else
+      @props.goToStep(@props.stepKey + 1)
     undefined # silence React return value warning

   goBackward: ->


Set start day for lic87

Set start day for lic87

--- server/calculations/ratingConstants.coffee
+++ server/calculations/ratingConstants.coffee
@@ -1,6 +1,6 @@
 export startDayForWeeks =
     "lic40": "2019-09-04"
-    "lic87": "2019-09-02"
+    "lic87": "2019-08-31"
     "zaoch": "2019-09-01"
     "stud": "2019-09-02"
     "notnnov": "2019-09-02"


Fix bad indentation

Fix bad indentation

--- src/plugins/markdown.coffee
+++ src/plugins/markdown.coffee
@@ -7,13 +7,13 @@
   smartypants: true

 pygmentsHighlight = (code, lang, callback) ->
-    pygments.colorize code, lang, "html", (data) ->
-      # Strip out the HTML wrapper added around the code
-      data = data.replace(
-        /^

([\s\S]+)<\/pre><\/div>$/img,
-        (match, p1, offset, str) -> p1
-      ).trim()
-      callback null, data
+  pygments.colorize code, lang, "html", (data) ->
+    # Strip out the HTML wrapper added around the code
+    data = data.replace(
+      /^

([\s\S]+)<\/pre><\/div>$/img,
+      (match, p1, offset, str) -> p1
+    ).trim()
+    callback null, data

 module.exports =
   converters:


pass ddp impl on to meteor middelware

pass ddp impl on to meteor middelware

--- imports/redux/middleware/index.coffee
+++ imports/redux/middleware/index.coffee
@@ -1,5 +1,7 @@
 { applyMiddleware } = require 'redux'
 { browserHistory }  = require 'react-router'
 nav = require './navigation.coffee'
+meteor = require './meteor.coffee'

-module.exports = applyMiddleware nav(browserHistory)
+module.exports = (ddp) ->
+   applyMiddleware nav(browserHistory), meteor(ddp)


StorageManager#loadObject defaults to an empty object for loadOptions.

--- vendor/assets/javascripts/brainstem/storage-manager.coffee
+++ vendor/assets/javascripts/brainstem/storage-manager.coffee
@@ -114,7 +114,7 @@
     throw "No expectation matched #{name} with #{JSON.stringify loader.originalOptions}"

   # Helpers
-  loadObject: (name, loadOptions, options = {}) ->
+  loadObject: (name, loadOptions = {}, options = {}) ->
     options = $.extend({}, { isCollection: true }, options)

     successCallback = loadOptions.success


Now correctly using BeanShell

Now correctly using BeanShell

--- web/doppio-jvm/scripts/demo/doppio-api.coffee
+++ web/doppio-jvm/scripts/demo/doppio-api.coffee
@@ -1,39 +1,32 @@
 "use strict"
 root = this
-
-# /home/doppio/vendor/classes/java/lang/Runtime.class - Causing magic number error as byte array is []

 root.init_editor = ->
     root.editor = ace.edit('source')
     JavaMode = require("ace/mode/java").Mode
     root.editor.getSession().setMode(new JavaMode())
-    root.editor.getSession().setValue ("public class Student {\n  public static void main(String[]args) {\n    System.out.println(\"Hello World\");\n  }\n}")
+    root.editor.getSession().setValue ('classes.doppio.JavaScript.eval("console.log(\\\"HELLO WORLD\\\");");')
     return

 root.preload = ->
     root.doppioAPI = new DoppioApi()
     root.init_editor()

-    # root.bs_cl = new ClassLoader.BootstrapClassLoader(read_classfile)
-
     $('#run_btn').click (e) ->
-        root.doppioAPI.compileAndRun root.editor.getValue()
+        root.doppioAPI.run root.editor.getValue()
         e.preventDefault()
         return
     $('#abort_btn').click (e) ->
-        console.log 'abort button clicked'
-        if doppioAPI.rs
-            $('#messages').text 'Stopping ...'
-            cb =-> $('#messages').text 'Stopped'
-            doppioAPI.rs.async_abort(cb)
-        else
-            console.log 'but nothing to do'
+        root.doppioAPI.abort()
         e.preventDefault()
         return
     return

 $(document).ready ->
     root.preload()
+
+### TEST CODE ###
+# classes.doppio.JavaScript.eval("console.log(\"HELLO WORLD\");");

 class window.DoppioApi
     ###
@@ -42,6 +35,8 @@
     ###
     constructor: () ->
         @load_mini_rt()
+        @bs_cl = new ClassLoader.BootstrapClassLoader(@read_classfile)
+        jvm.set_classpath '/home/doppio/vendor/classes/', './'
         return

     # Read in a binary classfile synchronously. Return an array of bytes.
@@ -79,59 +74,40 @@
         end_untar = (new Date()).getTime()
         console.log "Untarring took a total of #{end_untar-start_untar}ms."

-    compileAndRun: (studentCode) ->
-        fname = "Student.java"
-        cname = fname.slice 0, -5
-        console.log cname
+    run: (studentCode) ->
+        start_time = (new Date()).getTime()
+        fname = 'program.bsh'
         @saveFile fname, studentCode
-        msg = ''
         stdout = (str) ->
-            msg += str
             console.log str
             return str
-
         stdin = -> "\n"
-
         class_args = [fname]
-        exec_finish_cb = ->
-            console.log 'Done'
+        finish_cb = =>
+            end_time = (new Date()).getTime()
+            console.log 'Finished Run'
+            console.log "Took #{end_time - start_time}ms."
+            @rs = null
             return

-        compile_finished_cb = ->
-            console.log "Compilation Finished"
-            if msg.length == 0
-                @exec stdout, stdin, cname, class_args, exec_finish_cb
+        @rs = new runtime.RuntimeState(stdout, stdin, @bs_cl)
+        jvm.run_class(@rs, 'bsh/Interpreter', class_args, finish_cb)
+        return

-        @compile stdout, fname, compile_finished_cb.bind @
+    abort: =>
+        console.log 'User Abort Requested'
+        if @rs
+            console.log 'Aborting Run'
+            cb = =>
+                console.log 'Aborted Successfully'
+                @rs = null
+            @rs.async_abort(cb)
+        else
+            console.log 'No Run Detected'
+        return

     saveFile: (fname, contents) ->
         contents += '\n' unless contents[contents.length-1] == '\n'
         node.fs.writeFileSync(fname, contents)
         console.log("File saved as '#{fname}'.")
-
-    compile: (stdout, fname, finish_cb) ->
-        console.log "Compiling #{fname} ..."
-        start_compile = (new Date()).getTime()
-        # jvm.set_classpath '/home/doppio/vendor/classes/', './:/home/doppio'
-        jvm.set_classpath '/home/doppio/vendor/classes/', './'
-
-        user_input = (resume) -> resume ''
-        bs_cl = new ClassLoader.BootstrapClassLoader(@read_classfile)
-        rs = new runtime.RuntimeState(stdout, user_input, bs_cl)
-        @rs = rs
-        args = [ fname ]
-        my_cb = =>
-            end_compile = (new Date()).getTime()
-            @rs = null
-            console.log "javac took a total of #{end_compile-start_compile}ms."
-            console.log 'Compilation complete'
-            finish_cb()
-        jvm.run_class(rs, 'classes/util/Javac', args, my_cb)
-        return
-
-    exec: (stdout, stdin, class_name, class_args, finish_cb) ->
-        console.log "Running #{class_name}"
-        bs_cl = new ClassLoader.BootstrapClassLoader(@read_classfile)
-        rs = new runtime.RuntimeState(stdout, stdin, bs_cl)
-        jvm.run_class(rs, class_name, class_args, finish_cb)
         return


less log

less log

--- server/processes/mailbox_refresh_list.coffee
+++ server/processes/mailbox_refresh_list.coffee
@@ -23,7 +23,7 @@
     diffBoxesList: (callback) =>
         cozyBoxes = ramStore.getMailboxesByAccount @account.id
         @account.imap_getBoxes (err, imapBoxes) =>
-            log.debug "refreshBoxes#results", cozyBoxes
+            log.debug "refreshBoxes#results", cozyBoxes.length
             return callback err if err

             # find new imap boxes


Use real-time server for websockets

Use real-time server for websockets

--- services/web/config/settings.defaults.coffee
+++ services/web/config/settings.defaults.coffee
@@ -110,7 +110,7 @@
 	# then websocketsUrl should be the same as siteUrl, with your reverse
 	# proxy responible for sending websocket traffic to the websocket service
 	# rather than connecting directly.
-	websocketsUrl: "http://localhost:3000"
+	websocketsUrl: "http://localhost:3026"

 	# Where your instance of ShareLaTeX can be found publically. Used in emails
 	# that are sent out, generated links, etc.


EmailSender: update protocol setting

EmailSender: update protocol setting

--- workers/social/lib/social/config.email.coffee
+++ workers/social/lib/social/config.email.coffee
@@ -1,11 +1,9 @@
 {argv} = require 'optimist'
 KONFIG = require('koding-config-manager').load("main.#{argv.c}")
-{email} = KONFIG
+{email, protocol} = KONFIG

 host = email.host
-protocol = email.protocol
-
-email.protocol ?= if host is 'localhost' then 'http:' else 'https:'
+email.protocol = protocol
 email.protocol = email.protocol.split(':').shift()+':'

 module.exports = email


JStack: add status field for future implementation of softDelete

--- workers/social/lib/social/models/stack.coffee
+++ workers/social/lib/social/models/stack.coffee
@@ -76,6 +76,20 @@

       meta               : require 'bongo/bundles/meta'

+      status             :
+        type             : String
+        enum             : ["Wrong type specified!", [
+
+          # States which description ending with '...' means its an ongoing
+          # proccess which you may get progress info about it
+          #
+          "Initial"         # Initial state
+          "Terminating"     # Stack is getting destroyed...
+          "Terminated"      # Stack is destroyed, not exists anymore
+        ]]
+
+        default          : -> "Initial"
+

   @getStack = (account, _id, callback)->

@@ -134,6 +148,7 @@
   @getSelector = (selector, account)->
     selector ?= {}
     selector.originId = account.getId()
+    selector.status   = $ne: "Terminated"
     return selector

   @some$ = permit 'list stacks',
@@ -214,6 +229,9 @@
     ]

     success: (client, callback)->
+
+      # TODO Implement delete methods.
+      @update $set: status: "Terminating"

       JDomain  = require "./domain"
       JMachine = require "./computeproviders/machine"


no message

no message

--- lib/atom-smart-template.coffee
+++ lib/atom-smart-template.coffee
@@ -17,12 +17,6 @@
     @templatesRoot = path.join atom.getUserInitScriptPath(), '../', 'smart-templates'

     fsPlus.makeTreeSync(@templatesRoot)
-
-
-
-
-
-

     @subscriptions = new CompositeDisposable


:memo:

:memo:

[ci skip]

--- src/scope-descriptor.coffee
+++ src/scope-descriptor.coffee
@@ -2,7 +2,7 @@
 # root of the syntax tree to a token including _all_ scope names for the entire
 # path.
 #
-# Methods that take a `ScopeDescriptor` will also accept an {Array} of {Strings}
+# Methods that take a `ScopeDescriptor` will also accept an {Array} of {String}
 # scope names e.g. `['.source.js']`.
 #
 # You can use `ScopeDescriptor`s to get language-specific config settings via


Remove dynamo dependence for awssum

Remove dynamo dependence for awssum

Remove our dependency on the node dynamo module and use the awssum module
instead as it's better supported.

--- src/server/db/amazon.coffee
+++ src/server/db/amazon.coffee
@@ -3,8 +3,8 @@
 # It uses Dynamo as the metadata store and it uses S3 for snapshot storage as
 # Dynamo objects are limited to 64KB in size.
 #
-# In order to use this backend you must require the 'awssum', 'async' and the
-# 'dynamo' npm packages.
+# In order to use this backend you must require the 'awssum' and 'async' npm
+# packages.
 #
 # Example usage:
 #
@@ -191,7 +191,7 @@

   awssum = require('awssum')
   amazon = awssum.load('amazon/amazon')
-  dynamo = require('dynamo')
+  DynamoDB = awssum.load('amazon/dynamodb').DynamoDB
   S3 = awssum.load('amazon/s3').S3
   s3 = new S3({
     accessKeyId: options.amazon_access_key,
@@ -199,11 +199,11 @@
     region: amazon.US_EAST_1
   });

-  client = dynamo.createClient({
+  db = new DynamoDB({
     accessKeyId: options.amazon_access_key,
-    secretAccessKey: options.amazon_secret_key
+    secretAccessKey: options.amazon_secret_key,
+    region: amazon.US_EAST_1
   })
-  db = client.get(options.amazon_region)

   snapshots_table = options.amazon_dynamo_snapshots_table_name
   snapshots_bucket = options.amazon_s3_snapshots_bucket_name
@@ -248,7 +248,7 @@
         request.Item['c'] = { S: 't' } if options['compress']

         snapshots_rw_queue.push((c) ->
-          db.putItem(request, c)
+          db.PutItem(request, c)
         , 'write Snapshot('+docName+'-'+docData.v+')', cb)
       ]

@@ -268,7 +268,7 @@
       ]
     (error, results) ->
       if error?
-        if error.message? and error.message.match 'The conditional request failed'
+        if error.Body? and error.Body.message.match 'The conditional request failed'
           callback?('Document already exists')
         else if results? and results.write_metadata?
           console.error('Failed to save Snapshot('+docName+'-'+docData.v+') to S3: '+util.inspect(error))
@@ -300,7 +300,7 @@
         # TODO: This will only return the latest 1 MB of results, so if there
         # are more keys additional requests must be made.
         snapshots_ro_queue.push((c) ->
-          db.query(request, c)
+          db.Query(request, c)
         , 'query Snapshots('+docName+')', cb)

       list_operations: (cb) ->
@@ -313,13 +313,13 @@
         # TODO: This will only return the latest 1 MB of results, so if there
         # are more keys additional requests must be made.
         operations_ro_queue.push((c) ->
-          db.query(request, c)
+          db.Query(request, c)
         , 'query Operations('+docName+')', cb)

       delete_snapshots: ['list_snapshots', (cb, results) ->
-        return cb('Document does not exist', null) if results.list_snapshots.Count == 0
-
-        async.mapSeries(results.list_snapshots.Items,
+        return cb('Document does not exist', null) if results.list_snapshots.Body.Count == 0
+
+        async.mapSeries(results.list_snapshots.Body.Items,
           (item, cb) ->
             request =
               TableName: snapshots_table
@@ -329,8 +329,10 @@
               Expected:
                 doc:
                   Value: { S: item.doc.S }
+              ReturnValues: 'NONE'
+
             snapshots_rw_queue.push((c) ->
-              db.deleteItem(request, c)
+              db.DeleteItem(request, c)
             , 'delete Snapshot('+item.doc.S+'-'+item.v.N+')', cb)
           (error, result)->
             if error?
@@ -341,9 +343,9 @@
       ]

       delete_s3_snapshots: ['list_snapshots', (cb, results) ->
-        return cb(null, {}) if results.list_snapshots.Count == 0
-
-        async.forEachSeries(results.list_snapshots.Items,
+        return cb(null, {}) if results.list_snapshots.Body.Count == 0
+
+        async.forEachSeries(results.list_snapshots.Body.Items,
           (item, cb) ->
             params =
               BucketName: snapshots_bucket
@@ -361,9 +363,9 @@
       ]

       delete_operations: ['list_operations', (cb, results) ->
-        return cb(null, {}) if results.list_operations.Count == 0
-
-        async.forEachSeries(results.list_operations.Items,
+        return cb(null, {}) if results.list_operations.Body.Count == 0
+
+        async.forEachSeries(results.list_operations.Body.Items,
           (item, cb) ->
             request =
               TableName: operations_table
@@ -373,8 +375,10 @@
               Expected:
                 doc:
                   Value: { S: item.doc.S }
+              ReturnValues: 'NONE'
+
             operations_rw_queue.push((c) ->
-              db.deleteItem(request, c)
+              db.DeleteItem(request, c)
             , 'delete Operation('+item.doc.S+'-'+item.v.N+')', cb)
           (error)->
             if error?
@@ -385,7 +389,7 @@
       ]
     (error, results) ->
       if error?
-        if error.toString().match 'The conditional request failed'
+        if error.Body? and error.Body.message.match 'The conditional request failed'
           callback?('Document does not exist')
         else if error == 'Document does not exist'
           callback?(error)
@@ -414,13 +418,13 @@
           ConsistentRead: true

         snapshots_ro_queue.push((c) ->
-          db.query(request, c)
+          db.Query(request, c)
         , 'query Snapshot('+docName+')', cb)

       get_data: ['get_snapshot', (cb, results) ->
-        return cb('Document does not exist', null) unless results.get_snapshot.Count == 1
-
-        item = results.get_snapshot.Items[0]
+        return cb('Document does not exist', null) unless results.get_snapshot.Body.Count == 1
+
+        item = results.get_snapshot.Body.Items[0]
         params =
           BucketName: snapshots_bucket
           ObjectName: item.doc.S+'-'+item.v.N+'.snapshot'
@@ -432,8 +436,8 @@

       compressor: ['get_snapshot', 'get_data', (cb, results) ->
         cb(null,
-          text: new TextCompressor(results.get_snapshot.Items[0].c, options['timing'])
-          binary: new BinaryCompressor(results.get_snapshot.Items[0].c, options['timing'])
+          text: new TextCompressor(results.get_snapshot.Body.Items[0].c, options['timing'])
+          binary: new BinaryCompressor(results.get_snapshot.Body.Items[0].c, options['timing'])
         )
       ]

@@ -442,21 +446,21 @@
       ]

       meta: ['compressor', (cb, results) ->
-        results.compressor.text.decode(results.get_snapshot.Items[0].meta.S, cb)
+        results.compressor.text.decode(results.get_snapshot.Body.Items[0].meta.S, cb)
       ]
     (error, results) ->
       if error?
         if error == 'Document does not exist'
           callback?(error)
         else if results? and results.get_snapshot?
-          item = results.get_snapshot.Items[0]
+          item = results.get_snapshot.Body.Items[0]
           console.error('Failed to get snapshot data for Document('+item.doc.S+'-'+item.v.N+'): '+util.inspect(error))
           callback?('Failed to get snapshot data')
         else
           console.error('Failed to get snapshot metadata for Document('+docName+'): '+util.inspect(error))
           callback?('Failed to get snapshot metadata')
       else
-        item = results.get_snapshot.Items[0]
+        item = results.get_snapshot.Body.Items[0]

         try
           snapshot = JSON.parse(results.snapshot)
@@ -514,7 +518,7 @@
         request.Item['c'] = { S: 't' } if options['compress']

         snapshots_rw_queue.push((c) ->
-          db.putItem(request, c)
+          db.PutItem(request, c)
         , 'write Snapshot('+docName+'-'+docData.v+')', cb)
       ]

@@ -535,7 +539,7 @@
       ]
     (error, results) ->
       if error?
-        if error.message? and error.message.match 'The conditional request failed'
+        if error.Body? and error.Body.message.match 'The conditional request failed'
           callback?('Document already exists')
         else if results? and results.write_metadata?
           console.error('Failed to save Snapshot('+docName+'-'+docData.v+') to S3: '+util.inspect(error))
@@ -568,14 +572,13 @@
           TableName: operations_table
           HashKeyValue: { S: docName }
           ConsistentRead: true
-          RangeKeyCondition:
-            ComparisonOperator: 'BETWEEN'
-            AttributeValueList: [{ N: start.toString() }, { N: end.toString() }]
+          ComparisonOperator: 'BETWEEN'
+          AttributeValueList: [{ N: start.toString() }, { N: end.toString() }]

         # TODO: This is limited to returning 1MB of data at a time, we should
         # handle getting more.
         operations_ro_queue.push((c) ->
-          db.query(request, c)
+          db.Query(request, c)
         , 'query Operations('+docName+'-'+start+':'+end+')', cb)

     (error, results) ->
@@ -584,7 +587,7 @@
         callback?('Failed to fetch operations')
       else
         data = []
-        async.map(results.get_metadata.Items, (operation, cb) ->
+        async.map(results.get_metadata.Body.Items, (operation, cb) ->
           compressor = new TextCompressor(operation.c?, options['timing'])
           async.auto(
             snapshot: (c) ->
@@ -652,7 +655,7 @@
         request.Item['c'] = { S: 't' } if options['compress']

         operations_rw_queue.push((c) ->
-          db.putItem(request, c)
+          db.PutItem(request, c)
         , 'write Operation('+docName+'-'+opData.v+')', cb)
       ]
     (error, results) ->


offer a course subscription trial

offer a course subscription trial

--- src/smc-webapp/billing.cjsx
+++ src/smc-webapp/billing.cjsx
@@ -1712,10 +1712,21 @@
                 error   = {@props.error}
                 onClose = {=>@props.redux.getActions('billing').clear_error()} />

+    render_help_suggestion: ->
+
+            <Space/> If you have any questions at all, email <HelpEmailLink /> immediately.
+
+                <Space/> Contact us if you are purchasing a course subscription, but need a short trial
+                to test things out first.<Space/>
+
+
+
     render_suggested_next_step: ->
         cards    = @props.customer?.sources?.total_count ? 0
         subs     = @props.customer?.subscriptions?.total_count ? 0
         invoices = @props.invoices?.data?.length ? 0
+        help     = @render_help_suggestion()
+
         if cards == 0
             if subs == 0
                 # no payment sources yet; no subscriptions either: a new user (probably)
@@ -1725,7 +1736,7 @@
                     choose from either a monthly, yearly or semester-long plan.
                     You will not be charged until you select a specific subscription then click
                     "Add Subscription".
-                    If you have any questions at all, email <HelpEmailLink /> immediately.
+                    {help}

             else
                 # subscriptions but they deleted their card.
@@ -1745,14 +1756,14 @@
                 same subscription more than once to increase the number of upgrades).
                 You will be charged only after you select a specific subscription and click
                 "Add Subscription".
-                If you have any questions, email <HelpEmailLink /> immediately.
+                {help}

         else if invoices == 0
             # have payment source, subscription, but no invoices yet

                 Sign up for the same subscription package more than
                 once to increase the number of upgrades that you can use.
-                If you have any questions, email <HelpEmailLink /> immediately.
+                {help}

         else
             # have payment source, subscription, and at least one invoice
@@ -1760,7 +1771,7 @@
                 You may sign up for the same subscription package more than
                 once to increase the number of upgrades that you can use.
                 Past invoices and receipts are also available below.
-                If you have any questions, email <HelpEmailLink /> immediately.
+                {help}

     render_info_link: ->


Stupid typo fix: @

Stupid typo fix: @

--- js/src/crypto/AesCryptoService.coffee
+++ js/src/crypto/AesCryptoService.coffee
@@ -56,7 +56,7 @@
       return @abstractCryptoService.decrypt(@key, iv, contents)

     decryptToUint8Array: (blockCiphertext) ->
-      plaintext = decrypt(blockCiphertext)
+      plaintext = @decrypt(blockCiphertext)
       return new Uint8Array(_.map(string, (c) -> c.charCodeAt() ) )

   return AesCryptoService


reword help

reword help

--- src/smc-webapp/support.cjsx
+++ src/smc-webapp/support.cjsx
@@ -391,23 +391,23 @@
                     the CoCalc documentation.

-                    Trying to sign out?  Click on your name at the top, then click the
-                    "Sign out..." button in account settings.
-

-

-                    You bought a subscription but it does not work...
-

-

-                    All your files seem gone or you deleted your project...
-

-

-                    My Sage worksheet or Jupyter notebook is very slow or will not run...
-

-

-                    My Jupyter notebook keeps crashing with "Kernel terminated"...
-

-

-                    I have a question about how to use Sage...
+                    Trying to sign out?  Click on Account on the top right, then click
+                    "Sign out..." in Preferences.
+

+

+                    Bought a subscription but it does not work?
+

+

+                    Files or project seem gone?
+

+

+                    Sage worksheet or Jupyter notebook is very slow or will not run?
+

+

+                    Jupyter notebook keeps crashing with "Kernel terminated"?
+

+

+                    Have a question about how to use Sage?

                     Requesting that we install software? Fill out the form below and


Add tests for embed services and handle bbox parameters

--- test/src/promises-test.coffee
+++ test/src/promises-test.coffee
@@ -109,13 +109,13 @@
         .should.notify(done)
   describe 'Test embedding', ->
     embedUrl = baseUrl + '/embed'
-    # Helper function to get js string to be evaluated with
+    # Helper functions to get js string to be evaluated with
     # asserters.jsCondition
     isNearMapCenter = (location, delta = 1e-4) ->
-      INITIAL_CENTER = 'app.getRegion("map").currentView.map._initialCenter'
+      MAP_CENTER = 'app.getRegion("map").currentView.map.getCenter()'
       initialCenter =
-        lat: INITIAL_CENTER + '.lat'
-        lng: INITIAL_CENTER + '.lng'
+        lat: MAP_CENTER + '.lat'
+        lng: MAP_CENTER + '.lng'
       isNear = (x) =>
         'Math.abs(' + x + ') < ' + delta
       subLat = initialCenter.lat + ' - ' + location.lat
@@ -123,6 +123,14 @@
       subLng = initialCenter.lng + ' - ' + location.lng
       lngIsNear = isNear subLng
       return "#{latIsNear} && #{lngIsNear}"
+    containsBbox = (bbox) ->
+      MAP_BOUNDS = 'app.getRegion("map").currentView.map.getBounds()'
+      BBOX_BOUNDS = "new L.LatLngBounds( new L.LatLng(#{bbox.sw}), new L.LatLng(#{bbox.ne}))"
+      return "#{MAP_BOUNDS}.contains(#{BBOX_BOUNDS})"
+    containsPoint = (point) ->
+      MAP_BOUNDS = 'app.getRegion("map").currentView.map.getBounds()'
+      POINT = "new L.LatLng(#{point.lat}, #{point.lng})"
+      return "#{MAP_BOUNDS}.contains(#{POINT})"
     describe 'Test embedded addresses', ->
       embeds = [
         {
@@ -152,23 +160,28 @@
           it 'Should display popup with correct street address', (done) ->
             browser
             .get(embedUrl + embed.path)
-            .waitForElementByCssSelector(
+            .waitForElementsByCssSelector(
                 '.leaflet-popup-content > .unit-name',
                 asserters.isDisplayed,
                 delay,
-                pollFreq,
-              (err, el) ->
-                el.text (err, text) -> text.should.equal(embed.name)
+                pollFreq
             )
+            .then((els) ->
+              should.equal(els.length, 1)
+            )
+            .text().should.become(embed.name)
             .should.notify done

-          it 'Should display marker for address', (done) ->
-            browser
-              .waitForElementByCssSelector(
+          it 'Should display one marker for address', (done) ->
+            browser
+              .waitForElementsByCssSelector(
                 '.leaflet-overlay-pane svg path.leaflet-clickable',
                 asserters.isDisplayed,
                 delay,
                 pollFreq
+              )
+              .then((els) ->
+                  should.equal(els.length, 1)
               )
               .should.notify(done)

@@ -179,7 +192,6 @@
                 delay, pollFreq))
                 .should.notify(done)
         return
-
     describe 'Test embedded units', ->
       embeds = [
         {
@@ -188,29 +200,108 @@
           location:
             lat: 60.188812
             lng: 24.930822
+        },
+        {
+          path: '/unit/41047?bbox=60.18672,24.92038,60.19109,24.93742'
+          name: 'Uimastadion / Maauimala'
+          location:
+            lat: 60.188812
+            lng: 24.930822
+          bbox:
+            sw: '60.18672,24.92038'
+            ne: '60.19109,24.93742'
+
+        },
+        {
+          path: '/unit/40823'
+          name: 'Kumpulan maauimala'
+          location:
+            lat: 60.208702
+            lng: 24.958284
+        },
+        {
+          path: '/unit/40823?bbox=60.20661,24.94783,60.21098,24.96489'
+          name: 'Kumpulan maauimala'
+          location:
+            lat: 60.208702
+            lng: 24.958284
+          bbox:
+            sw: '60.20661,24.94783'
+            ne: '60.21098,24.96489'
         }
       ]
-
       embeds.map (embed) ->
         describe embed.path, ->
-          it 'Should display popup with the unit name', (done) ->
+          it 'Should display one popup with the unit name', (done) ->
             browser
               .get(embedUrl + embed.path)
-              .waitForElementByCssSelector(
+              .waitForElementsByCssSelector(
                 '.leaflet-popup-content > .unit-name',
                 asserters.isDisplayed,
                 delay,
-                pollFreq,
-                (err, el) ->
-                  el.text (err, text) -> text.should.equal(embed.name)
-              )
+                pollFreq
+              )
+              .then((els) ->
+                  should.equal(els.length, 1)
+              )
+              .text().should.become(embed.name)
               .should.notify done
-          it 'Should display marker icon', (done) ->
+          it 'Should display one marker icon', (done) ->
             browser
               .waitForElementsByCssSelector('.leaflet-marker-pane > .leaflet-marker-icon', asserters.isDisplayed, delay, pollFreq)
+              .then((els) ->
+                should.equal(els.length, 1)
+              )
               .should.notify done
-          it 'Should be centered to the unit', (done) ->
-            browser
-              .waitFor(asserters.jsCondition(isNearMapCenter(embed.location)),
-              delay, pollFreq)
-              .should.notify(done)
+          unless embed.bbox
+            it 'Should be centered to the unit', (done) ->
+                browser
+                  .waitFor(asserters.jsCondition(isNearMapCenter(embed.location)),
+                  delay, pollFreq)
+                  .should.notify(done)
+          else
+            it 'Should contain unit location', (done) ->
+              browser
+              .waitFor(asserters.jsCondition(containsPoint(embed.location)),
+                  delay, pollFreq)
+              .should.notify(done)
+            it 'Should contain bbox', (done) ->
+              browser
+              .waitFor(asserters.jsCondition(containsBbox(embed.bbox)),
+                  delay, pollFreq)
+              .should.notify(done)
+        return
+    describe 'Test embedded services', ->
+      embeds = [
+        {
+          path: '/unit?service=25002&bbox=60.13744,24.77468,60.20935,25.04703&city=helsinki'
+          bbox:
+            sw: '60.13744,24.77468'
+            ne: '60.20935,25.04703'
+        },
+        {
+          path: '/unit?service=25010&bbox=60.13744,24.77468,60.20935,25.04703&city=helsinki'
+          bbox:
+            sw: '60.13744,24.77468'
+            ne: '60.20935,25.04703'
+        }
+      ]
+      embeds.map (embed) ->
+        describe embed.path, ->
+          it 'Title should become "Pääkaupunkiseudun palvelukartta"', (done) ->
+            browser
+              .get(embedUrl + embed.path)
+              .title().should.become(pageTitle)
+              .should.notify(done)
+          it 'Should have marker icons', (done) ->
+            browser
+              .waitForElementsByCssSelector('.leaflet-marker-pane > .leaflet-marker-icon', delay, pollFreq)
+              .then((els) ->
+                els.length.should.be.greaterThan(1)
+              )
+              .should.notify done
+          it 'Should contain the bbox', (done) ->
+            browser
+            .waitFor asserters.jsCondition(containsBbox(embed.bbox)), delay, pollFreq
+            .should.notify done
+        return


just test buttons

just test buttons

--- src/test/test_render.coffee
+++ src/test/test_render.coffee
@@ -78,6 +78,15 @@
     t.end()

   test "render buttons", (t) ->
+    body = render_mock(controls).body
+    t.equal body.tag, 'div', 'body.tag'
+    t.equal get_label(body), controls._LABEL, 'controls label'
+
+    button = body.body[0]
+    t.equal button.tag, 'span', 'button span'
+    t.ok style = button.attr.style, 'button style'
+    t.ok style.border, 'button border'
+    t.ok style.margin, 'button margin'
     t.end()

   test "render selection", (t) ->


bugix in crop

bugix in crop

--- modules/gui/platforms/node/tools/images.coffee
+++ modules/gui/platforms/node/tools/images.coffee
@@ -56,6 +56,6 @@
     ##
     crop: (geometry, src, dest, done)->

-        im.convert [src, '-region', geometry, dest], done
+        im.convert ['-crop', geometry, src, dest], done

-                +


reverted extensionsbox

reverted extensionsbox

--- client/landing/app/Applications/Environments.kdapplication/views/domains/domaincreationform.coffee
+++ client/landing/app/Applications/Environments.kdapplication/views/domains/domaincreationform.coffee
@@ -69,7 +69,7 @@
               defaultValue            : "subdomain"
               radios                  : domainOptions
               change                  : =>
-                {DomainOption, domainName, domains, regYears, extensionSelect} = @forms["Domain Address"].inputs
+                {DomainOption, domainName, domains, regYears} = @forms["Domain Address"].inputs
                 {checkButton, registerButton} = @forms["Domain Address"].buttons
                 actionState = DomainOption.getValue()
                 domainName.setValue ''
@@ -78,7 +78,6 @@
                     @suggestionBox?.show()
                     # domainName.setValidation domainNameValidation
                     domains.hide()
-                    extensionSelect.show()
                     regYears.show()
                     createButton.hide()
                     checkButton.show()
@@ -88,7 +87,6 @@
                     @suggestionBox?.hide()
                     # domainName.setValidation domainNameValidation
                     domains.hide()
-                    extensionSelect.hide()
                     regYears.hide()
                     checkButton.hide()
                     registerButton.hide()
@@ -97,7 +95,6 @@
                   when "subdomain"
                     # domainName.unsetValidation()
                     @suggestionBox?.hide()
-                    extensionSelect.hide()
                     domains.show()
                     regYears.hide()
                     checkButton.hide()
@@ -134,14 +131,6 @@
                       required        : yes
                     messages          :
                       required        : "Please select a parent domain."
-                extensionSelect       :
-                  cssClass            : "domains hidden"
-                  itemClass           : KDSelectBox
-                  validate            :
-                    rules             :
-                      required        : yes
-                    messages          :
-                      required        : "Please select an extension"
             suggestionBox             :
               type                    : "hidden"
     form = @forms["Domain Address"]
@@ -157,22 +146,17 @@
       for domain in userDomains
         if not domain.regYears > 0
           domainList.push {title:".#{domain.domain}", value:domain.domain}
-      {domains, extensionSelect} = @forms["Domain Address"].inputs
+      {domains} = @forms["Domain Address"].inputs
       domains.setSelectOptions domainList
-      availableTldList = ["com", "net", "org"]
-      extensionsList = ({title:'.'+ext, value:ext} for ext in availableTldList)
-      extensionSelect.setSelectOptions extensionsList
-      extensionSelect.setDefaultValue availableTldList[0]

   checkAvailability: ->
     form = @forms["Domain Address"]
-    domain            = form.inputs.domainName.getValue()
-    tld               = form.inputs.extensionSelect.getValue()
+    domainInput       = domainName
+    domainName        = form.inputs.domainName.getValue()
+    splittedDomain    = domainName.match(/([\w\-]+)\.(.*)/)
+    domain            = splittedDomain[1]
+    tld               = splittedDomain[2]
     {createButton, checkButton, registerButton} = form.buttons
-    unless domain.match(/^\w+$/)
-      notifyUser "Please select the extension from the select box"
-      checkButton.hideLoader()
-      return
     @clearSuggestions()
     KD.remote.api.JDomain.getTldPrice tld, (tldPrice) =>
       KD.remote.api.JDomain.isDomainAvailable domain, tld, (avErr, status, price, suggestions) =>
@@ -205,47 +189,46 @@
     {createButton, registerButton} = form.buttons
     @clearSuggestions()

-    {DomainOption, domainName, regYears, domains, extensionSelect} = form.inputs
+    {DomainOption, domainName, regYears, domains} = form.inputs
     domainInput       = domainName
-    domain            = domainName.getValue()
-    tld               = extensionSelect.getValue()
-    unless domain.match(/^\w+$/)
-      notifyUser "Please select the extension from the select box"
-      registerButton.hideLoader()
-      return
+    domainName        = form.inputs.domainName.getValue()
+
     domainOptionValue = DomainOption.getValue()

     if domainOptionValue is 'new'
-        form = @forms["Domain Address"]
-        {createButton, registerButton} = form.buttons
-        paymentController = KD.getSingleton('paymentController')
-        group             = KD.getSingleton("groupsController").getCurrentGroup()
-        registerTheDomain = ->
-            KD.remote.api.JDomain.registerDomain
-              domainName : domainInput.getValue()
-              years      : regYears.getValue()
-            , (err, domain) =>
-              if err
-                warn err
-                console.log err
-                notifyUser "An error occured. Please try again later."
-              else
-                @showSuccess domain
-                domain.setDomainCNameToProxyDomain()
-              registerButton.hideLoader()
-
-        paymentController.getBillingInfo 'user', group, (err, account)->
-          need = err or not account or not account.cardNumber
-          if need
-            paymentController.setBillingInfo 'user', group, (success)->
-              if success
-                registerTheDomain()
-          else
-            registerTheDomain()
+      splittedDomain    = domainName.match(/([\w\-]+)\.(.*)/)
+      domain            = splittedDomain[1]
+      tld               = splittedDomain[2]
+      form = @forms["Domain Address"]
+      {createButton, registerButton} = form.buttons
+      paymentController = KD.getSingleton('paymentController')
+      group             = KD.getSingleton("groupsController").getCurrentGroup()
+      registerTheDomain = ->
+          KD.remote.api.JDomain.registerDomain
+            domainName : domainInput.getValue()
+            years      : regYears.getValue()
+          , (err, domain) =>
+            if err
+              warn err
+              console.log err
+              notifyUser "An error occured. Please try again later."
+            else
+              @showSuccess domain
+              domain.setDomainCNameToProxyDomain()
+            registerButton.hideLoader()
+
+      paymentController.getBillingInfo 'user', group, (err, account)->
+        need = err or not account or not account.cardNumber
+        if need
+          paymentController.setBillingInfo 'user', group, (success)->
+            if success
+              registerTheDomain()
+        else
+          registerTheDomain()

     else if domainOptionValue is 'existing'
-      @createDomain {domain, regYears:0, domainType:'existing'}, (err, domain)=>
+      @createDomain {domainName, regYears:0, domainType:'existing'}, (err, domain)=>
         createButton.hideLoader()
         if err
           warn err
@@ -258,12 +241,12 @@

     else # create a subdomain
       subDomainPattern = /^([a-z0-9]([_\-](?![_\-])|[a-z0-9]){0,60}[a-z0-9]|[a-z0-9])$/
-      unless subDomainPattern.test domain
+      unless subDomainPattern.test domainName
         createButton.hideLoader()
         return notifyUser "#{domainName} is an invalid subdomain."
-      domain = "#{domainName}.#{domains.getValue()}"
-
-      @createDomain {domain, regYears:0, domainType:'subdomain'}, (err, domain)=>
+      domainName = "#{domainName}.#{domains.getValue()}"
+
+      @createDomain {domainName, regYears:0, domainType:'subdomain'}, (err, domain)=>
         createButton.hideLoader()
         if err
           warn err
@@ -314,7 +297,7 @@
   showSuccess:(domain) ->
     @clearSuggestions()
     form            = @forms["Domain Address"]
-    {domainName, extensionSelect}    = form.inputs
+    {domainName}    = form.inputs
     {suggestionBox} = form.fields
     {close, createButton, cancel, another} = form.buttons

@@ -333,7 +316,7 @@
       # partial : "Thank you!
Your domain #{domainName.getValue()} has been added to our database. Please go to your provider's website and add a CNAME record mapping to kontrol.in.koding.com."

       # change this part when registering is there.
-      partial : "Thank you!
Your subdomain #{domainName.getValue()}.#{extensionSelect.getValue()} has been added to our database. You can dismiss this panel and point your new domain to one of your VMs on the settings screen."
+      partial : "Thank you!
Your subdomain #{domainName.getValue()} has been added to our database. You can dismiss this panel and point your new domain to one of your VMs on the settings screen."
       click   : => @reset()


:bug: Pass `this` into the promise function

--- lib/atom-io-client.coffee
+++ lib/atom-io-client.coffee
@@ -195,7 +195,7 @@
       gzip: true
     }

-    new Promise (resolve, reject) ->
+    new Promise (resolve, reject) =>
       request options, (err, res, body) =>
         if err
           error = new Error("Searching for \u201C#{query}\u201D failed.")


Check if view is available

Check if view is available

--- lib/pdf-status-bar-view.coffee
+++ lib/pdf-status-bar-view.coffee
@@ -27,7 +27,7 @@
   updatePdfStatus: ->
     view = atom.workspaceView.getActiveView()

-    if view.pdfDocument
+    if view and view.pdfDocument
       @getPdfStatus(view)
     else
       @pdfStatus.hide()


disallow dropping on past days

disallow dropping on past days

--- tutor/src/components/course-calendar/month.cjsx
+++ tutor/src/components/course-calendar/month.cjsx
@@ -162,7 +162,7 @@
     @props.date?.format?('MMMM')

   onDrop: (item, offset) ->
-    return unless @state.hoveredDay
+    return unless @state.hoveredDay and @state.hoveredDay.isSameOrAfter(TimeStore.getNow(), 'day')
     if item.pathname # is a link to create an assignment
       url = item.pathname + "?" + qs.stringify({
         due_at: @state.hoveredDay.format(@props.dateFormat)


Fix typo

Fix typo

--- index.cjsx
+++ index.cjsx
@@ -144,7 +144,7 @@
       notifySecretary: -1

   render: ->
-

+

       <link rel="stylesheet" href={join(relative(ROOT, __dirname), 'assets', 'secretary.css')} />


do not check twice on startup

do not check twice on startup

--- lib/atom-ternjs-manager.coffee
+++ lib/atom-ternjs-manager.coffee
@@ -21,7 +21,6 @@

   constructor: (provider) ->
     @provider = provider
-    @checkGrammarSettings()
     @helper = new Helper(this)
     @registerHelperCommands()
     @provider.init(this)


Use true custom element

Use true custom element

--- lib/grammar-status-view.coffee
+++ lib/grammar-status-view.coffee
@@ -1,7 +1,7 @@
 {Disposable} = require 'atom'

 # View to show the grammar name in the status bar.
-class GrammarStatusView extends HTMLElement
+class GrammarStatusView extends HTMLDivElement
   initialize: (@statusBar) ->
     @classList.add('grammar-status', 'inline-block')
     @grammarLink = document.createElement('a')
@@ -61,4 +61,4 @@
     else
       @style.display = 'none'

-module.exports = document.registerElement('grammar-selector-status', prototype: GrammarStatusView.prototype, extends: 'div')
+module.exports = document.registerElement('grammar-selector-status', prototype: GrammarStatusView.prototype)


Require jquery from vendor/

Require jquery from vendor/

--- space-pen.coffee
+++ space-pen.coffee
@@ -1,5 +1,5 @@
 if typeof require is 'function'
-  $ = jQuery = require('jquery')
+  $ = jQuery = require('./vendor/jquery')
 else
   $ = jQuery = window.jQuery


:lipstick::fire: Whitespaces

:lipstick::fire: Whitespaces

--- lib/minimap-editor-view.coffee
+++ lib/minimap-editor-view.coffee
@@ -80,7 +80,6 @@

     @cachedScrollTop = scrollTop
     @requestUpdate()
-

   getMinimapHeight: -> @getLinesCount() * @getLineHeight()
   getLineHeight: -> 3
@@ -200,7 +199,6 @@
     else
       @drawLines(firstRow, lastRow, 0, @context)

-
     # copy displayed canvas to offscreen canvas
     @offscreenCanvas.width = @lineCanvas[0].width
     @offscreenCanvas.height = @lineCanvas[0].height


Fix for difftool testing

Fix for difftool testing

--- lib/models/git-difftool.coffee
+++ lib/models/git-difftool.coffee
@@ -18,9 +18,12 @@
     console.warn("Git-plus: no tree-view or sublime-tabs package loaded")

   isFolder = false
-  if packageObj?.selectedPath
-    isFolder = fs.isDirectorySync packageObj.selectedPath
-    file ?= repo.relativize(packageObj.selectedPath)
+  if not file
+    if packageObj?.selectedPath
+      isFolder = fs.isDirectorySync packageObj.selectedPath
+      file ?= repo.relativize(packageObj.selectedPath)
+  else
+    isFolder = fs.isDirectorySync file

   file ?= repo.relativize(atom.workspace.getActiveTextEditor()?.getPath())
   if not file


Remove debug code

Remove debug code

--- lib/notational-velocity.coffee
+++ lib/notational-velocity.coffee
@@ -95,9 +95,6 @@
       atom.notifications.addError('notational-velocity.directory #{prevNoteDirectory} does not exists. Migration process is failed.')
       return

-    alert(prevNoteDirectory)
-    alert(currNoteDirectory)
-
     if prevNoteDirectory.startsWith(packagesDirectory)
       fs.makeTreeSync(currNoteDirectory)
       fs.copySync(prevNoteDirectory, currNoteDirectory)