panTo markers

panTo markers

--- public/js/map.coffee
+++ public/js/map.coffee
@@ -82,9 +82,8 @@
 			fillOpacity: 0.5

 		marker.on 'click', () ->
+			map.panTo ad.location.coordinates
 			onMarkerClick ad.id
-			console.log ad.location.coordinates
-			map.panTo ad.location.coordinates
 		marker.on 'mouseover', () ->
 			circle.addTo map
 		marker.on 'mouseout', () ->


Fix single-image subject display

Fix single-image subject display

--- src/subject-viewer.coffee
+++ src/subject-viewer.coffee
@@ -57,7 +57,9 @@

     widths = []
     heights = []
-    for imgSrc, i in @subject.location.standard then do (i) =>
+
+    subjectImages = [].concat @subject.location.standard
+    for imgSrc, i in subjectImages then do (i) =>
       @addFrame imgSrc, (image) =>
         widths.push image.attr 'width'
         heights.push image.attr 'height'
@@ -67,12 +69,14 @@
         @markingSurface.el.style.height = "#{maxHeight}px"
         @frameGroup.attr transform: "translate(#{maxWidth / 2}, #{maxHeight / 2})"

-        if i + 1 is @subject.location.standard.length
+        if i + 1 is subjectImages.length
+          @goTo 0
           callback?()

       @addToggle i

-    @playButton.prop 'disabled', @subject.location.standard.length is 1
+    @playButton.prop 'disabled', subjectImages.length is 1
+    @togglesList.find('button').prop 'disabled', subjectImages.length is 1

   addFrame: (imgSrc, callback) ->
     loadImage imgSrc, ({src, width, height}) =>


Disabled test PDF parsing.

Disabled test PDF parsing.

--- server/pdf.coffee
+++ server/pdf.coffee
@@ -70,4 +70,5 @@
   processError = (message, exception) ->
     console.error "PDF Error", message, exception

-  PDFJS.getDocument({data: pdfFile, password: ''}).then processPDF, processError
+  # TODO: Uncomment to see how it works
+  #PDFJS.getDocument({data: pdfFile, password: ''}).then processPDF, processError


Set the correct MSVS environment on Win32

--- src/installer.coffee
+++ src/installer.coffee
@@ -11,6 +11,8 @@

 config = require './config'
 Command = require './command'
+
+isWin32 = !!process.platform.match(/^win/)

 module.exports =
 class Installer extends Command
@@ -34,6 +36,7 @@
     installNodeArgs.push("--target=#{config.getNodeVersion()}")
     installNodeArgs.push("--dist-url=#{config.getNodeUrl()}")
     installNodeArgs.push('--arch=ia32')
+
     env = _.extend({}, process.env, HOME: @atomNodeDirectory)

     mkdir(@atomDirectory)
@@ -53,6 +56,7 @@
     installArgs.push("--target=#{config.getNodeVersion()}")
     installArgs.push('--arch=ia32')
     installArgs.push('--silent') if options.argv.silent
+    installArgs.push('--msvs_version=2012') if isWin32
     env = _.extend({}, process.env, HOME: @atomNodeDirectory)

     installDirectory = temp.mkdirSync('apm-install-dir-')
@@ -77,6 +81,7 @@
     installArgs.push("--target=#{config.getNodeVersion()}")
     installArgs.push('--arch=ia32')
     installArgs.push('--silent') if options.argv.silent
+    installArgs.push('--msvs_version=2012') if isWin32
     env = _.extend({}, process.env, HOME: @atomNodeDirectory)

     @fork @atomNpmPath, installArgs, {env}, (code, stderr='', stdout='') ->


:art:

:art:

--- src/token-iterator.coffee
+++ src/token-iterator.coffee
@@ -124,7 +124,7 @@
         @closeTags = @containingTags.slice()
         @containingTags.length = 0
         @openTags = EMPTY_ARRAY
-        @tagsToReopenAfterFold = @decorationIterator.seek(@getEndBufferPosition())
+        @tagsToReopenAfterFold = @decorationIterator.seek(@endBufferPosition)
       else
         decorationIteratorPosition = @decorationIterator.getPosition()


Limit nominations to one per user, closes #3

--- src/aotw.coffee
+++ src/aotw.coffee
@@ -68,6 +68,17 @@
             while i >= 0
                 nomination = @storage.nominations[i]
                 if nomination["url"] == url
+                    return false
+                i--
+            return true
+        else return true
+
+    uniqueUser = (user) ->
+        if @storage.nominations && @storage.nominations.length > 0
+            i = @storage.nominations.length - 1
+            while i >= 0
+                nomination = @storage.nominations[i]
+                if nomination["user"] == user
                     return false
                 i--
             return true
@@ -164,17 +175,19 @@
     nominate: (msg) ->
         if msg.match[1] != "nominate"
             url = msg.match[1].split(" ")[1]
+            user = msg.message.user.name.toLowerCase()
             if validUrl url
                 if uniqueNomination url
-                    user = msg.message.user.name.toLowerCase()
-                    try
-                        @storage.nominations.push(user: user, url: url)
-                    catch
-                        @storage.nominations = []
-                        @storage.nominations.push(user: user, url: url)
-                    finally
-                        @save
-                        msg.send "Nomination saved"
+                    if uniqueUser user
+                        try
+                            @storage.nominations.push(user: user, url: url)
+                        catch
+                            @storage.nominations = []
+                            @storage.nominations.push(user: user, url: url)
+                        finally
+                            @save
+                            msg.send "Nomination saved"
+                    else msg.send "Invalid nomination: you already nominated an album"
                 else msg.send "Invalid nomination: duplicate url"
             else msg.send "Invalid nomination: invalid url"
         else msg.send "Invalid nomination: missing url"


Handle loading class in browser

Handle loading class in browser

--- lib/TemplateConfig.coffee
+++ lib/TemplateConfig.coffee
@@ -1,5 +1,8 @@
+# handle CommonJS/Node.js or browser

-sysmo = require 'sysmo'
+sysmo ||= (require?) ? require('sysmo') : window.Sysmo
+
+# class definition

 class TemplateConfig
   constructor: (config) ->
@@ -84,6 +87,11 @@
     pair.key = key if 'key' not of pair
     pair.value = value if 'value' not of pair
     pair
-
-# register module
-module.exports = TemplateConfig+
+# register module (CommonJS/Node.js) or handle browser
+
+if module?
+  module.exports = TemplateConfig
+else
+  window.json2json or= {}
+  window.json2json.TemplateConfig = TemplateConfig


support booleans :)

support booleans :)

--- lib/addcodesharing.coffee
+++ lib/addcodesharing.coffee
@@ -20,10 +20,11 @@

 # Map of functions that can convert various Javascript objects to strings.
 types =
-
+
   function: (fn) -> "#{ fn }"
   string: (s) -> "\"#{ s }\""
   number: (n) -> n.toString()
+  boolean: (n) -> n.toString()

   object: (obj) ->

@@ -65,11 +66,17 @@

+
+
+
+
 exports.addCodeSharingTo = (app) ->

   # Set default scripts-directory
   if not app.set "clientscripts"
     app.set "clientscripts", "#{ process.cwd() }/clientscripts"
+
+

   # Path to a directory of client-side only scripts.
@@ -103,13 +110,13 @@
   # Functions that will executed immediately in browser when loaded.
   clientExecs = []

-  # Function for getting all script tags.
+  # Function for getting all script tags.
   # Configure will create this.
   getScriptTags = null

   # Collect shared variables and code and wrap them in a closure for browser
-  # execution.
+  # execution.
   runOnListen.push ->

     # Create common namespace for shared code
@@ -134,7 +141,7 @@
     # "this" will be REALLYEXPRESS instead of global window.
     compiledEmbeddedCode += "}).call(REALLYEXPRESS);\n"
     compiledEmbeddedCode = beautify compiledEmbeddedCode
-
+

   app.configure "development", ->
@@ -168,27 +175,40 @@

       return compiledTags = tags.join "\n"

-
-    # We will allow usage of production.js only in production mode
+    for script in clientScriptsFs
+      script = "#{ scriptDir }/#{ script }"
+      console.log "compile #{ script }"
+      if script.match /\.js$/
+        productionClientCode +=  fs.readFileSync(script).toString()
+      else if script.match /\.coffee$/
+        productionClientCode += coffeescript.compile fs.readFileSync(script).toString()
+
+
+    # We will allow usage of production.js only in production mode
     runOnListen.push ->
-      for script in clientScriptsFs
-        script = "#{ scriptDir }/#{ script }"
-        console.log "compile #{ script }"
-        if script.match /\.js$/
-          productionClientCode +=  fs.readFileSync(script).toString()
-        else if script.match /\.coffee$/
-          productionClientCode += coffeescript.compile fs.readFileSync(script).toString()
-
       productionClientCode += compiledEmbeddedCode
       productionClientCode = minify productionClientCode

-
-    # All js code will be shared from here in production
-    app.get "/managedjs/production.js", (req, res) ->
-      # TODO: Set cache time to forever. Timestamps will kill the cache when
-      # required.
-      res.send productionClientCode, 'Content-Type': 'application/javascript'
-
+      # All js code will be shared from here in production
+      app.get "/managedjs/production.js", (req, res) ->
+        # TODO: Set cache time to forever. Timestamps will kill the cache when
+        # required.
+        res.send productionClientCode, 'Content-Type': 'application/javascript'
+
+
+
+
+
+
+
+  app.use (req, res, next) ->
+    console.log "testing"
+    console.log this
+    console.log this == app
+    res.share = () ->
+      console.log "SHARER!!!"
+    res.codeee = "CODE"
+    next()

   # Dynamic helper for templates. bundleJavascript will return all required
@@ -232,7 +252,7 @@
              , ('Content-Type': 'text/plain'), 404

-
+
   # Extends Express server object with function that will share given Javascript
   # object with browser. Will work for functions too, but be sure that you will
   # use only pure functions. Scope or context will not be same in the browser.
@@ -261,7 +281,7 @@
       scriptURLs.unshift url for url in obj.reverse()
     else
       scriptURLs.unshift obj
-
+

   # Run when app starts listening a port


Mention what ENOENT check ignores

Mention what ENOENT check ignores

--- lib/atom-io-client.coffee
+++ lib/atom-io-client.coffee
@@ -129,7 +129,7 @@
     deleteAvatar = (child) =>
       avatarPath = path.join(@getCachePath(), child)
       fs.unlink avatarPath, (error) ->
-        if error.code isnt 'ENOENT'
+        if error.code isnt 'ENOENT' # Ignore cache paths that don't exist
           console.warn("Error deleting avatar (#{error.code}): #{avatarPath}")

     fs.readdir @getCachePath(), (error, _files) =>


attempt to fix https://github.com/cs150bf/ever-notedown/issues/72

--- lib/ever-notedown.coffee
+++ lib/ever-notedown.coffee
@@ -1483,7 +1483,7 @@
       if noteID?
         note = noteHelper.findNote(window.evnd.noteIndex, {id: noteID})
       else
-        note = searchOpenedNote()
+        note = @searchOpenedNote()
     return unless note?
     window.evnd.enHelper ?= new evernoteHelper.EvernoteHelper()
     window.evnd.enHelper.getENML note, null, (enml) =>


improve the vessel list

improve the vessel list

--- imports/ui/pages/vessels/vessels.coffee
+++ imports/ui/pages/vessels/vessels.coffee
@@ -16,24 +16,26 @@
 			vessels = Vessels.find {}, sort: name: 1

 			columns = [
-				{ name: 'id', title: '', breakpoints: '', filterable: false }
+				{ name: '_id', title: '', breakpoints: '', visible: false, filterable: false }
+				{ name: 'id', title: '#', breakpoints: '', filterable: false }
 				{ name: 'name', title: TAPi18n.__('vessels.name'), breakpoints: '' }
-				{ name: 'flag', title: TAPi18n.__('vessels.flag'), breakpoints: 'xs' }
-				{ name: 'type', title: TAPi18n.__('vessels.type'), breakpoints: 'xs' }
+				{ name: 'flag', title: TAPi18n.__('vessels.flag'), breakpoints: 'xs', filterable: false }
+				{ name: 'type', title: TAPi18n.__('vessels.type'), breakpoints: 'xs', filterable: false }
 				{ name: 'callsign', title: TAPi18n.__('vessels.callsign'), breakpoints: '' }
 				{ name: 'eni', title: TAPi18n.__('vessels.eni'), breakpoints: 'xs' }
 				{ name: 'imo', title: TAPi18n.__('vessels.imo'), breakpoints: 'xs' }
 				{ name: 'mmsi', title: TAPi18n.__('vessels.mmsi'), breakpoints: 'xs' }
-				{ name: 'lastVisit', title: TAPi18n.__('vessels.lastVisit'), breakpoints: 'xs' }
-				{ name: 'harborGroup', title: TAPi18n.__('vessels.harborGroup'), breakpoints: 'xs' }
-				{ name: 'nextVisit', title: TAPi18n.__('vessels.nextVisit'), breakpoints: 'xs' }
-				{ name: 'languages', title: TAPi18n.__('vessels.languages'), breakpoints: 'xs' }
+				{ name: 'lastVisit', title: TAPi18n.__('vessels.lastVisit'), breakpoints: 'xs', filterable: false }
+				{ name: 'harborGroup', title: TAPi18n.__('vessels.harborGroup'), breakpoints: 'xs', filterable: false }
+				{ name: 'nextVisit', title: TAPi18n.__('vessels.nextVisit'), breakpoints: 'xs', filterable: false }
+				{ name: 'languages', title: TAPi18n.__('vessels.languages'), breakpoints: 'xs', filterable: false }
 			]

 			rows = []

 			for vessel, index in vessels.fetch()
 				rows.push
+					_id: vessel._id
 					id: index + 1
 					name: vessel.name
 					flag: vessel.flag
@@ -60,8 +62,7 @@
 					alwaysShow: true
 					allowAdd: false
 					allowDelete: false
-					editRow: (row) ->
-						wrs -> FlowRouter.setQueryParams editVessels: row.value._id
+					editRow: (row) -> wrs ->

 	@autorun ->
 		FlowRouter.getParam('language') # redraw with new language
@@ -79,8 +80,8 @@

 Template.vessels.events

+	'keyup #vesselSearch': (e) ->
+		searchString.set(e.target.value)
+
 	'click #addVessel': ->
 		wrs -> FlowRouter.setQueryParams addVessel: true
-
-	'keyup #vesselSearch': (e) ->
-		searchString.set(e.target.value)


Append view if index is at end

Append view if index is at end

--- lib/directory-view.coffee
+++ lib/directory-view.coffee
@@ -37,10 +37,10 @@

     @subscribe @directory, 'entry-added', (entry, index) =>
       view = @createViewForEntry(entry)
-      if index is 0
-        @entries.prepend(view)
+      if index < @entries.children().length
+        @entries.children().eq(index).before(view)
       else
-        @entries.children().eq(index).before(view)
+        @entries.append(view)

       @trigger 'tree-view:directory-modified'


Rollback c379c1c and fix #67

Rollback c379c1c and fix #67

--- lib/linter-pylama.coffee
+++ lib/linter-pylama.coffee
@@ -193,6 +193,7 @@
   makeLintInfo: (fileName, originFileName) =>
     originFileName = fileName if not originFileName
     filePath = path.normalize path.dirname(originFileName)
+    tmpFilePath =  if fileName != originFileName then path.dirname(fileName) else filePath
     projectPath = atom.project.relativizePath(originFileName)[0]
     env = @initEnv filePath, projectPath
     args = @initArgs filePath
@@ -209,7 +210,7 @@
       args: args
       options:
         env: env
-        cwd: projectPath
+        cwd: tmpFilePath
         stream: 'both'


tests for date regex

tests for date regex


remote build

remote build

--- lib/server-script.coffee
+++ lib/server-script.coffee
@@ -31,7 +31,7 @@
       atom.notifications.addError "Error processing #{name}: " + e.message,
                                    dismissable: true
   doRsync: (cmd) ->
-    cmdStr = 'rsync -a' + cmd
+    cmdStr = 'rsync -a ' + cmd
     console.log cmdStr
     try
       return execSync cmdStr, timeout:5e3, encoding:'utf8', maxBuffer:10e6
@@ -48,77 +48,30 @@
       loc = @setup.server.location
       atom.notifications.addError "Error executing command #{cmdStr} on #{loc}: " + e.message,
                                    dismissable: true
-  save: ->
+  save: (action) ->
     if not fs.existsSync @serverScriptFolder then @initSetupFolder(); return
     if not (@setup = @loadCsonInitFile 'server-setup.cson') or
        not (secret = @loadCsonInitFile 'secrets.cson') then return
     pwdStr = (if secret.login.password then ':' + secret.login.password else '')
     usrStr = (if secret.login.user then secret.login.user + pwdStr + '@' else '')
     @server = usrStr + @setup.server.location
+    root =  path.normalize @setup.server.projectRoot
+    remotePath = @server + ':' + root
     # console.log @server, '\n', @doSSH 'ls -al'

     if @setup.options.syncChangedFiles
       gitIgnStr = (if @setup.options.useGitignore \
                    then " --exclude=.git --filter=':- .gitignore' " else ' ')
-      remotePath = @server + ':' + path.normalize @setup.server.projectRoot
-      @doRsync gitIgnStr + "#{@rootDirPath}/ #{remotePath}/\n"
-
-    # runScript = fs.readFileSync 'init-setup-folder/.run-server-script.sh', 'utf8'
-    # runPath = path.join @serverScriptFolder, '.run-server-script.sh'
-    # fs.writeFileSync runPath, runScript
-
-  #
-  #
-  #
-  #
-  # save: ->
-  #
-  #   atom.workspace.open('line-count.txt').then (editor) =>
-  #
-  #     filewalker(rootDirPath, maxPending: 4).on("file", (path, stats, absPath) =>
-  #         sfxMatch = /\.([^\.]+)$/.exec path
-  #         if sfxMatch and
-  #             (sfx = sfxMatch[1]) in suffixes and
-  #             path.indexOf('node_modules') is -1 and
-  #             path.indexOf('bower_components') is -1 and
-  #             (not @gitignore or @gitignore.accepts path)
-  #
-  #           code = fs.readFileSync absPath, 'utf8'
-  #           code = code.replace /\r/g, ''
-  #           try
-  #             counts = sloc code, sfx
-  #           catch e
-  #             add 'Warning: ' + e.message
-  #             return
-  #
-  #           dirParts = path.split '/'
-  #           dir = ''
-  #           for dirPart, idx in dirParts
-  #             if idx is dirParts.length-1 then break
-  #             dir += dirPart
-  #             addAttrs dir, dirs, counts
-  #             dir += '/'
-  #           files[path] = counts
-  #           addAttrs sfx, typeData, counts
-  #           addAttrs  '', total,    counts
-  #
-  #       ).on("error", (err) ->
-  #         add err.message
-  #
-  #       ).on("done", ->
-  #         add '\nLine counts for project ' + rootDirPath + '.'
-  #         add 'Generated by the Atom editor package Line-Count on ' +
-  #             moment().format 'MMMM D YYYY H:mm.'
-  #         add 'Counts are in order of source, comments, and total.'
-  #
-  #         printSection 'Files',       files
-  #         printSection 'Directories', dirs
-  #         printSection 'Types',       typeData
-  #         printSection 'Total',       total
-  #
-  #         editor.setText text
-  #
-  #       ).walk()
+      @doRsync gitIgnStr + "#{@rootDirPath}/ #{remotePath}/"
+
+    scripts = (if action is 'save' then @setup.options.scriptsRunOnSave \
+                                   else @setup.options.scriptsRunOnCommand)
+    if scripts.length > 0
+      for script in scripts
+        remoteScript = "#{remotePath}/#{script}"
+        @doRsync "#{@serverScriptFolder}/#{script} #{remoteScript}"
+        @doSSH "chmod +x #{root}/#{script}"
+        @doSSH "#{root}/#{script}"

   deactivate: ->
     @subs.dispose()


Export setPosition function

Export setPosition function

--- tooltips.coffee
+++ tooltips.coffee
@@ -51,6 +51,7 @@
 	set: setTooltip
 	get: getTooltip
 	hide: hideTooltip
+	setPosition: setPosition

 # Template helpers


Update tr.cson

Update tr.cson

--- lib/translations/tr.cson
+++ lib/translations/tr.cson
@@ -6,6 +6,7 @@
 "yes": "Evet"
 "no" : "Hayır"
 "emptyList" : "Burada bir şey yok!"
+"enabled": "Aktif"
 "menu":
     "title": "Menü"
     "home": "Ana Sayfa"
@@ -66,6 +67,7 @@
     "offlineMode": "Çevrimdışı mod (yakında)"
     "fontSize": "Yazı Boyutu"
     "language": "Dil"
+    "pushNotifications": "Push Bildirimleri"
 "languages":
     "en": "İngilizce"
     "tr": "Türkçe"


Typo fix

Typo fix

--- src/lib/Graph.coffee
+++ src/lib/Graph.coffee
@@ -843,7 +843,7 @@
     return unless req.readyState is 4
     unless req.status is 200
       return callback new Error "Failed to load #{url}: HTTP #{req.status}"
-    callback null, eq.responseText
+    callback null, req.responseText
   req.open 'GET', url, true
   req.send()


changed comment headings in utils.coffee

--- src/lib/utils.coffee
+++ src/lib/utils.coffee
@@ -4,7 +4,8 @@
 # For all details and documentation:
 # <http://neocotic.com/template>

-#### Private variables
+# Private variables
+# -----------------

 # Mapping for internationalization handlers.
 # Each handler represents an attribute (based on the property name) and is
@@ -43,7 +44,8 @@
 # which is used by `i18nProcess` to query all elements.
 i18nSelector   = "[#{i18nAttributes.join '],['}]"

-#### Private functions
+# Private functions
+# -----------------

 # Find all elements to be internationalized and call their corresponding
 # handler(s).
@@ -65,19 +67,21 @@
       break
   return subs

-#### Utilities setup
+# Utilities setup
+# ---------------

 utils = window.utils =

-  #### Data functions
+  # Data functions
+  # --------------

   # Determine whether or not the specified key exists in `localStorage`.
   exists: (key) ->
     return localStorage.hasOwnProperty key

   # Retrieve the value associated with the specified key from `localStorage`.
-  # If the value is found, parse it as JSON before being returning it; otherwise
-  # return `undefined`.
+  # If the value is found, parse it as JSON before being returning it;
+  # otherwise return `undefined`.
   get: (key) ->
     value = localStorage[key]
     return if value? then JSON.parse value else value
@@ -97,8 +101,8 @@

   # Copy the value of the existing key to that of the new key then remove the
   # old key from `localStorage`.
-  # If the old key doesn't exist in `localStorage`, assign the specified default
-  # value to it instead.
+  # If the old key doesn't exist in `localStorage`, assign the specified
+  # default value to it instead.
   rename: (oldKey, newKey, defaultValue) ->
     if utils.exists oldKey
       utils.init newKey, utils.get oldKey
@@ -114,7 +118,8 @@
     localStorage[key] = if value? then JSON.stringify value else value
     return oldValue

-  #### Internationalization functions
+  # Internationalization functions
+  # ------------------------------

   # Convenient shorthand for `chrome.i18n.getMessage`.
   i18n: ->


Add get distribution method, without testing and test data

--- app/routes/index.coffee
+++ app/routes/index.coffee
@@ -178,11 +178,47 @@
                     .json message:'Internal Server Error'

+
     getDistributions: (req, res) ->
         return

+
+
     getDistributionsOf: (req, res) ->
-        return
+        # parse params
+        identifier = req.params.identifier
+        res
+            .header 'Content-Type', 'application/json; charset=utf-8'
+            .header 'Access-Control-Allow-Origin', '*'
+
+        promise1 = Name
+            .find {rank:'species', ja:identifier}
+            .select '_id'
+            .exec()
+            .then (results) ->
+                if results.length < 1
+                    res.
+                        .status 404
+                        .json  message:'Unknown bird name'
+                    throw new Error 'Unknown bird name'
+                else
+                    return results[0]
+            .then ({id})->
+                Distribution
+                    .find {name_id: id}
+                    .exec()
+                    .then (results) ->
+                        res
+                            .status 200
+                            .json {name:identifier, distributions:results}
+
+            .catch (err) ->
+                console.log err
+                res
+                    .status 500
+                    .json message:'Internal Server Error'
+
+

     postDistributionsOf: (req, res) ->
         return


do some map reader things

do some map reader things

--- src/mapreader.coffee
+++ src/mapreader.coffee
@@ -1,5 +1,14 @@
 zlib = require('zlib');
 fs = require('fs');
+
+unpack: (str) ->
+    b = new ArrayBuffer(str.length*8);
+    ba = new Float64Array(b);
+    len = str.length;
+    for i in len
+        ba[i] = str.charCodeAt(i);
+    return b;
+

 class gg2MapReader
     constructor: () ->
@@ -16,5 +25,7 @@
         @pos = @mapstr.indexOf(@KEYWORD);
         if (@pos is false)
             return "";
-        @len =
+        @len = unpack(@mapstr.substring(@pos - 8, @pos - 4))[1] - @KEYWORD.length;
+        @leveldata = @mapstr.substring(@pos + @KEYWORD.length + 2, @len);

+


Browser.wait re-throws exceptions if callback is provided. Needed to play nicely with mocha and assertions.

--- lib/zombie/browser.coffee
+++ lib/zombie/browser.coffee
@@ -289,7 +289,7 @@
     promise = @eventLoop.wait(waitDuration, completionFunction)

     if callback
-      promise.then(callback, callback)
+      promise.then(callback, callback).done()
     return promise


Correct Linter Validation tests.

Correct Linter Validation tests.

--- spec/helpers-spec.coffee
+++ spec/helpers-spec.coffee
@@ -7,7 +7,22 @@
     expect( -> Helpers.validateResults('String')).toThrow()

 describe "The Linter Validation Helper", ->
-  it "should throw an exception when nothing is passed.", ->
-    expect( -> Helpers.validateLinter()).toThrow()
-  it "should throw an exception when a String is passed.", ->
-    expect( -> Helpers.validateLinter('String')).toThrow()
+  it "should throw an exception when grammarScopes is not an Array.", ->
+    linter = {
+      grammarScopes: 'not an array'
+      lint: ->
+    }
+    expect( -> Helpers.validateLinter(linter)).toThrow()
+
+  it "should throw an exception when lint is missing.", ->
+    linter = {
+      grammarScopes: []
+    }
+    expect( -> Helpers.validateLinter(linter)).toThrow()
+
+  it "should throw an exception when a lint is not a function.", ->
+    linter = {
+      grammarScopes: []
+      lint: 'not a function'
+    }
+    expect( -> Helpers.validateLinter(linter)).toThrow()


Make specs green when running from within Atom

The node_modules check when iterating over the stack prevented
the specs from passing when running from atom/atom since the
path used contained `node_modules` in it.

Tweak the spec to use paths that will always be the same.

--- spec/metrics-spec.coffee
+++ spec/metrics-spec.coffee
@@ -172,8 +172,27 @@
     it "reports a deprecation without metadata specified", ->
       Reporter.request.reset()
       jasmine.snapshotDeprecations()
-      anotherLineInTheStack = -> grim.deprecate('bad things are bad')
-      anotherLineInTheStack()
+
+      stack = [
+        {
+          fileName: '/Applications/Atom.app/pathwatcher.js'
+          functionName: 'foo'
+          location: '/Applications/Atom.app/pathwatcher.js:10:5'
+        }
+        {
+          fileName: '/Users/me/.atom/packages/metrics/lib/metrics.js'
+          functionName: 'bar'
+          location: '/Users/me/.atom/packages/metrics/lib/metrics.js:161:5'
+        }
+      ]
+      deprecation =
+        message: "bad things are bad"
+        stacks: [stack]
+      grim.addSerializedDeprecation(deprecation)
+
+      spyOn(atom.packages.getLoadedPackage('metrics').mainModule, 'getPackagePathsByPackageName').andReturn
+        metrics: '/Users/me/.atom/packages/metrics'
+
       jasmine.restoreDeprecationsSnapshot()

       waitsFor ->


Remove reference to private atom.project.repositoryForDirectory property in unit test.

--- spec/project-spec.coffee
+++ spec/project-spec.coffee
@@ -214,19 +214,18 @@

     it "resolves to a GitRepository and is cached when the given directory is a Git repo", ->
       the_result = "dummy_value"
-      the_promise = null
       directory = new Directory(path.join(__dirname, '..'))

       waitsForPromise ->
-        the_promise = atom.project.repositoryForDirectory(directory)
-        the_promise.then (result) -> the_result = result
+        atom.project.repositoryForDirectory(directory).then (result) -> the_result = result

       runs ->
         dirPath = directory.getRealPathSync()
         expect(the_result).toBeInstanceOf GitRepository
         expect(the_result.getPath()).toBe path.join(dirPath, '.git')
-        expect(atom.project.repositoryPromisesByPath.size).toBe 1
-        expect(atom.project.repositoryPromisesByPath.get dirPath).toBe the_promise
+
+        # Verify that the result is cached.
+        expect(atom.project.repositoryForDirectory(directory)).toBe(atom.project.repositoryForDirectory(directory))

   describe ".setPaths(path)", ->
     describe "when path is a file", ->


Document hidden feature to switch between javascript and coffeescript.

--- src/pry/commands/xecute.coffee
+++ src/pry/commands/xecute.coffee
@@ -4,7 +4,9 @@

 class Xecute extends Command

-  name: ''
+  name: 'mode'
+  definition: 'Switched between CoffeeScript and JavaScript execution.'
+  help: 'Type `mode` to switch between using JavaScript or CoffeeScript.'

   last_error: null


Remove/add the *subscription*, not the child

--- src/pane-axis.coffee
+++ src/pane-axis.coffee
@@ -96,11 +96,11 @@
   subscribeToChild: (child) ->
     subscription = child.onDidDestroy => @removeChild(child)
     @subscriptionsByChild.set(child, subscription)
-    @subscriptions.add(child)
+    @subscriptions.add(subscription)

   unsubscribeFromChild: (child) ->
     subscription = @subscriptionsByChild.get(child)
-    @subscriptions.remove(child)
+    @subscriptions.remove(subscription)
     subscription.dispose()

   destroyed: ->


Update SearchSubscription to support basic multiaccount search... seems to work?

--- src/search-subscription.coffee
+++ src/search-subscription.coffee
@@ -8,7 +8,7 @@

 class SearchSubscription extends MutableQuerySubscription

-  constructor: (@_terms, @_accountId) ->
+  constructor: (@_terms, @_accountIds) ->
     super(null, {asResultSet: true})

     @_version = 0
@@ -30,15 +30,21 @@
   retrievePage: (idx) =>
     version = @_version += 1

-    NylasAPI.makeRequest
-      method: 'GET'
-      path: "/threads/search?q=#{encodeURIComponent(@_terms)}"
-      accountId: @_accountId
-      json: true
-      returnsModel: true
-    .then (threads) =>
+    requests = @_accountIds.map (aid) =>
+      NylasAPI.makeRequest
+        method: 'GET'
+        path: "/threads/search?q=#{encodeURIComponent(@_terms)}"
+        accountId: aid
+        json: true
+        returnsModel: true
+
+    Promise.all(requests).then (resultArrays) =>
       return unless @_version is version
-      query = DatabaseStore.findAll(Thread).where(id: _.pluck(threads, 'id'))
+      resultIds = []
+      for resultArray in resultArrays
+        resultIds = resultIds.concat _.pluck(resultArray, 'id')
+
+      query = DatabaseStore.findAll(Thread).where(id: resultIds).order(Thread.attributes.lastMessageReceivedTimestamp.descending())
       @replaceQuery(query)

 module.exports = SearchSubscription


Clarify ::subscribe exception message for various use cases

--- src/space-pen.coffee
+++ src/space-pen.coffee
@@ -413,11 +413,33 @@
 # Deprecations

 View::subscribe = ->
-  throw new Error """
-    `subscribe` is no longer available. Please use native the `addEventListener`,
-    jQuery's `on` or subscribe to commands via `atom.commands.add`. See the
-    docs at https://atom.io/docs/api/latest/CommandRegistry#instance-add.
-  """
+  message = "The `::subscribe` method is no longer available on SpacePen views.\n\n"
+
+  if arguments.length is 1
+    message += """
+      To store multiple subscription objects for later disposal, add them to a
+      `CompositeDisposable` instance (https://atom.io/docs/api/v0.150.0/CompositeDisposable)
+      and call `.dispose()` on it explicitly in this view's `::detached` hook.
+    """
+  else
+    if arguments[0]?.jquery
+      message += """
+        To subscribe to events on a jQuery object, use the traditional `::on` and
+        `::off methods`.
+      """
+    else
+      message += """
+        To subscribe to events on an Atom object, use an explicit event-subscription
+        method (starting with ::onDid* or ::onWill*).
+
+        To collect multiple subscription objects for later disposal, add them to a
+        `CompositeDisposable` instance:
+        https://atom.io/docs/api/v0.150.0/CompositeDisposable
+
+        Call `.dispose()` on your `CompositeDisposable` in this view's `::detached` hook.
+      """
+
+  throw new Error(message)

 $.fn.command = (eventName, handler) ->
   throw new Error """


Only listen for existing attribute changes; when we do re-render, pass in the user data so admin stuff works

--- apps/artwork/client/view.coffee
+++ apps/artwork/client/view.coffee
@@ -54,7 +54,8 @@
       @belowTheFoldView.setupLayeredSearch()

     # Re-fetch and update based on changes
-    @artwork.on 'change', @renderDetail, this
+    _.each _.keys(@artwork.attributes), (attr) =>
+      @artwork.on "change:#{attr}", @renderDetail, this
     @artwork.fetch()

   setupRelatedLayers: ->
@@ -90,7 +91,11 @@
     @setupArtistArtworkSaveButtons @artist.artworks

   renderDetail: ->
-    @$('.artwork-detail').html detailTemplate(artwork: @artwork, artist: @artist)
+    @$('.artwork-detail').html detailTemplate
+      artwork:  @artwork
+      artist:   @artist
+      user:     @currentUser
+      sd:       require('sharify').data
     @followButton.setElement @$('.artwork-artist-follow-button')

   setupArtistArtworkSaveButtons: (artworks) ->


Clean things up.

Clean things up.

--- apps/partner/client/view.coffee
+++ apps/partner/client/view.coffee
@@ -70,19 +70,13 @@
   initializeTablist: ->
     @sections = @getDisplayableSections @getSections()

-    # If we don't have the @currentSection set yet here,
-    # it is safe to use the default overview section to render the tablis.
-    # After we route to a section, we will highlight the current tab.
-    #
-    # Here we just need to make sure we set up correct tablist.
-
     # If the partner doesn't have its default tab displayable,
-    # e.g. `Overview` for galleries or `Shows` for institutions,
+    # e.g. /overview for galleries or /shows for institutions,
     # we display the first tab content of displayable tabs.
     unless _.contains @sections, @currentSection
       @renderSection (@currentSection = @sections?[0])

-    # render tablist
+    # Render tablist
     @$('.partner-nav').html( tablistTemplate
       profile: @profile
       sections: @sections


syncstring: address some cases that lead to "Cannot read property 'set' of undefined" in the log

--- src/smc-util/syncstring.coffee
+++ src/smc-util/syncstring.coffee
@@ -1164,15 +1164,21 @@
                     @_save(cb)

     _set_save: (x) =>
-        @_syncstring_table.set?(@_syncstring_table.get_one()?.set('save', immutable.fromJS(x)))
+        if @_closed # nothing to do
+            return
+        @_syncstring_table?.set?(@_syncstring_table.get_one()?.set('save', immutable.fromJS(x)))
         return

     _set_read_only: (read_only) =>
-        @_syncstring_table.set?(@_syncstring_table.get_one()?.set('read_only', read_only))
+        if @_closed # nothing to do
+            return
+        @_syncstring_table?.set?(@_syncstring_table.get_one()?.set('read_only', read_only))
         return

     get_read_only: () =>
-        @_syncstring_table?.get_one()?.get('read_only')
+        if @_closed # nothing to do
+            return
+        return @_syncstring_table?.get_one()?.get('read_only')

     wait_until_read_only_known: (cb) =>
         if not @_syncstring_table?


at least link from the share server to cocalc!

--- src/smc-webapp/share/page.cjsx
+++ src/smc-webapp/share/page.cjsx
@@ -34,6 +34,16 @@
             title += " - #{@props.subtitle}"
         <title>{title}</title>

+    cocalc_link: ->
+        if @props.viewer == 'embed'
+

+                Powered by CoCalc
+

+        else
+

+                CoCalc lets you use Linux, Python, LaTeX, Jupyter notebooks and much more in your browser! Pivately share with collaborators. Changes are synchronized in realtime.
+

+
     notranslate: ->
         # don't translate the index pages
         return null if not @props.notranslate
@@ -68,6 +78,7 @@

                 {@title()}
+                {@cocalc_link()}
                 {@notranslate()}
                 {### bootstrap CDN ###}
                 <link
@@ -160,7 +171,7 @@
                 Open in {site_name}

-

+

             <CoCalcLogo base_url={base_url} /> Shared


add a stupid warning about project collab and video chat

--- src/smc-webapp/video-chat.cjsx
+++ src/smc-webapp/video-chat.cjsx
@@ -24,6 +24,8 @@
 {Button}      = require('react-bootstrap')
 {sha1}        = require('smc-util/schema').client_db
 {server_time} = require('./webapp_client').webapp_client
+
+{alert_message} = require('./alerts')

 {React, ReactDOM, rclass, redux, rtypes, Redux} = require('./app-framework')
 {Icon, Tip, SetIntervalMixin} = require('./r_misc')
@@ -86,6 +88,8 @@
     # The canonical secret chatroom id.
     chatroom_id: ->
         secret_token = redux.getStore('projects').getIn(['project_map', @project_id, 'status', 'secret_token'])
+        if not secret_token
+            alert_message(type:'error', message:"You MUST be a project collaborator -- video chat will fail.")
         return sha1(secret_token, @path)

     # Open the video chat window, if it isn't already opened


remove debug output

remove debug output

--- imports/ui/viz/DataStoreUsageMeter.cjsx
+++ imports/ui/viz/DataStoreUsageMeter.cjsx
@@ -21,8 +21,6 @@
       dataPoint 'Free', (Math.round free.value), 'unset'
     ]

-    console.log 'series', series
-
     <AnnotatedMeter legend={true}
       type='circle'
       size='small'


replace all slashes instead of the first one

--- config.coffee
+++ config.coffee
@@ -8,7 +8,7 @@

 #- regJoin replace a single '/' with '[\/\\]' so it can handle either forward or backslash
-regJoin = (s) -> new RegExp(s.replace(/\//, '[\\\/\\\\]'))
+regJoin = (s) -> new RegExp(s.replace(/\//g, '[\\\/\\\\]'))

 #- Build the config


Score files with a low nesting depth higher.

Fixes #692

--- src/stdlib/fuzzy-filter.coffee
+++ src/stdlib/fuzzy-filter.coffee
@@ -1,4 +1,5 @@
 stringScore = require 'stringscore'
+path = require 'path'

 module.exports = (candidates, query, options={}) ->
   if query
@@ -6,9 +7,13 @@
       string = if options.key? then candidate[options.key] else candidate
       score = stringScore(string, query)

-      # Basename matches count for more.
       unless /\//.test(query)
-        score += stringScore(string.replace(/^.*\//,''), query)
+        # Basename matches count for more.
+        score += stringScore(path.basename(string), query)
+
+        # Shallow files are scored higher
+        depth = Math.min(1, 10 - string.split('/').length - 1)
+        score *= depth * 0.01

       { candidate, score }


Pruning.

Pruning.

--- miniLockLib.coffee
+++ miniLockLib.coffee
@@ -1,19 +1,10 @@
-if module?.exports? and require?
-  Base58      = require '../lib/base58'
-  BLAKE2s     = require '../lib/blake2s'
-  nacl        = require 'tweetnacl'
-  scrypt      = require '../lib/scrypt-async'
-  zxcvbn      = require 'zxcvbn'
-  miniLockLib = module.exports = {}
-else
-  Base58      = this.Base58
-  BLAKE2s     = this.BLAKE2s
-  nacl        = this.nacl
-  scrypt      = this.scrypt
-  zxcvbn      = this.zxcvbn
-  miniLockLib = this.miniLockLib = {}
-
-
+Base58  = this.Base58
+BLAKE2s = this.BLAKE2s
+nacl    = this.nacl
+scrypt  = this.scrypt
+zxcvbn  = this.zxcvbn
+
+miniLockLib = this.miniLockLib = {}

 # --------------
@@ -31,7 +22,7 @@
 # Email Addresses
 # ---------------
 #
-# miniLock only accepts standards compliant email addresses.
+# miniLock only accepts relatively standards compliant email addresses.
 #
 miniLockLib.emailAddressIsAcceptable = (emailAddress) ->
   EmailAddressPattern.test(emailAddress)
@@ -113,7 +104,7 @@
 # The last number is a checksum that was is derived by hashing your public key with BLAKE2
 # with a 1-byte output.
 # After constructing the 33 bytes of the miniLock ID, it is encoded
-# into a Base58 representation, meant to be easily communicable via email or instant messaging.
+# into a Base58 representation.
 #
 miniLockLib.makeID = (publicKey) ->
   if publicKey?.length is 32


Cleanup

Cleanup

--- client/models/user.coffee
+++ client/models/user.coffee
@@ -7,7 +7,9 @@
   urlRoot: "#{sd.API_URL}/users"

   isEditorialTeam: ->
-    (@get('type') is 'Admin' and @get('email')?.split('@')[0] in sd.EDITORIAL_TEAM?.split(',')) or @get('email') is sd.EDITORIAL_EMAIL
+    (@get('type') is 'Admin' and
+      @get('email')?.split('@')[0] in sd.EDITORIAL_TEAM?.split(',')) or
+        @get('email') is sd.EDITORIAL_EMAIL

   isAdmin: ->
     @get('type') is 'Admin'


installed warnings when leaving routes or page

--- client/src/app/app.coffee
+++ client/src/app/app.coffee
@@ -40,7 +40,18 @@
   $urlRouterProvider.otherwise( '/game/list' )
 )

-.controller('AppCtrl', ( $scope, $location ) ->
+.controller('AppCtrl', ( $scope, $location, $window ) ->
+  $scope.$on '$stateChangeStart', (event, toState, toParams, fromState, fromParams) ->
+    console.log("fromState", fromState, "toState", toState)
+
+    # Warn about losing changes if the leaves the edit game screen
+    if fromState.name is "game-edit" and not window.confirm("You will lose all your changes. Are you sure?")
+      event.preventDefault()
+      $window.history.back()
+
+    # Warn about losing editing changes when the user navigates away to a different site
+    window.onbeforeunload = if toState.name is "game-edit" then -> "You will lose all your changes. Are you sure?"
+
   $scope.$on '$stateChangeSuccess', (event, toState, toParams, fromState, fromParams) ->
     if angular.isDefined( toState.data.pageTitle )
       $scope.pageTitle = toState.data.pageTitle + ' | RedWire'


PointLayout now has a nicer inspectedObjects output

--- src/art/engine/layout/point_layout_base.coffee
+++ src/art/engine/layout/point_layout_base.coffee
@@ -5,7 +5,10 @@

   {point, Point} = Atomic
   {point0} = Point
-  {BaseObject, log, inspect, isFunction, isNumber, isPlainObject, nearInfinity, nearInfinityResult} = Foundation
+  {
+    BaseObject, log, inspect, isFunction, isNumber, isPlainObject, nearInfinity, nearInfinityResult
+    inspectedObjectLiteral
+  } = Foundation

   class PointLayoutBase extends BaseObject

@@ -36,6 +39,9 @@
     @getter inspectedString: -> @toString()

     inspect: -> @toString()
+
+    @getter
+      inspectedObjects: -> inspectedObjectLiteral @toString()

     @getter """
       xRelativeToParentW


Indexed element getter helper

Indexed element getter helper

--- test/e2e/extensions/elements.coffee
+++ test/e2e/extensions/elements.coffee
@@ -12,3 +12,5 @@
 shrub.text = (selector) -> (element `by`.css selector).getText()

 shrub.count = (selector) -> (element.all `by`.css selector).count()
+
+shrub.get = (selector, index = 0) -> (element.all `by`.css selector).get index


Update publish:github task to only reuse drafts

--- tasks/publish.coffee
+++ tasks/publish.coffee
@@ -20,6 +20,7 @@
       token: process.env.GITHUB_TOKEN
       manifest: manifest
       reuseRelease: true
+      reuseDraftOnly: true
       draft: true

 # Upload deb and RPM packages to Bintray


.cson -> .json

.cson -> .json

--- build/tasks/compile-packages-slug-task.coffee
+++ build/tasks/compile-packages-slug-task.coffee
@@ -10,7 +10,7 @@

   getMenu = (appDir) ->
     menusPath = path.join(appDir, 'menus')
-    menuPath = path.join(menusPath, "#{process.platform}.cson")
+    menuPath = path.join(menusPath, "#{process.platform}.json")
     menu = CSON.readFileSync(menuPath) if fs.isFileSync(menuPath)
     rm menusPath
     menu ? {}


fix null target.

fix null target.

--- chat/grails-app/assets/javascripts/chat.coffee
+++ chat/grails-app/assets/javascripts/chat.coffee
@@ -393,7 +393,7 @@
         num = parseInt($('#nowNumberBadge').text()) + 1
         $('#nowNumberBadge').text num

-    if msg.dmtarget isnt ''
+    if msg.dmtarget isnt '' and msg.dmtarget isnt 'null'
         msg.text = "@#{msg.dmtarget} #{msg.text}"

     if (msg.status is 'log' and lastUserLog is msg.username) or (msg.status isnt 'log' and lastUser is msg.username)


pass user variable in withConvertedUser helper function

--- workers/social/testhelper/index.coffee
+++ workers/social/testhelper/index.coffee
@@ -85,13 +85,13 @@
   withDummyClient context, ({ client }) ->
     JUser.convert client, userFormData, (err, data) ->
       expect(err).to.not.exist
-      { account, newToken }      = data
-      client.sessionToken        = newToken
-      client.connection.delegate = account
+      { account, newToken, user } = data
+      client.sessionToken         = newToken
+      client.connection.delegate  = account

       if opts?.userFormData?
-      then callback { client, account, sessionToken : newToken }
-      else callback { client, account, sessionToken : newToken, userFormData }
+      then callback { client, user, account, sessionToken : newToken }
+      else callback { client, user, account, sessionToken : newToken, userFormData }

 generateDummyUserFormData = (opts = {}) ->


Add test for 8 byte wide (non-nullable) Ints

also extends the existing test for nullable variable width integers

--- test/unit/token/row-token-parser-test.coffee
+++ test/unit/token/row-token-parser-test.coffee
@@ -3,6 +3,58 @@
 dataTypeByName = require('../../../lib/token/data-type').typeByName
 WritableBuffer = require('../../../lib/tracking-buffer/tracking-buffer').WritableTrackingBuffer

+writeBytes = (buffer, array) ->
+  for b in array
+    do (b) ->
+      buffer.writeUInt8( b )
+
+module.exports.bigint = (test) ->
+  colMetaData = [{type: dataTypeByName.BigInt},
+                 {type: dataTypeByName.BigInt},
+                 {type: dataTypeByName.BigInt},
+                 {type: dataTypeByName.BigInt},
+                 {type: dataTypeByName.BigInt},
+                 {type: dataTypeByName.BigInt},
+                 {type: dataTypeByName.BigInt},
+                 {type: dataTypeByName.BigInt},
+                 {type: dataTypeByName.BigInt},
+                 {type: dataTypeByName.BigInt},
+                 {type: dataTypeByName.BigInt}]
+
+  buffer = new WritableBuffer(0)
+
+  buffer.writeUInt8(TYPE.ROW)
+  writeBytes( buffer, [0,0,0,0,0,0,0,0,
+                       1,0,0,0,0,0,0,0,
+                       255,255,255,255,255,255,255,255,
+                       2,0,0,0,0,0,0,0,
+                       254,255,255,255,255,255,255,255,
+                       255,255,255,255,255,255,255,127,
+                       0,0,0,0,0,0,0,128,
+                       10,0,0,0,0,0,0,0,
+                       100,0,0,0,0,0,0,0,
+                       232,3,0,0,0,0,0,0,
+                       16,39,0,0,0,0,0,0] )
+  buffer = buffer.data
+
+  token = parser(buffer, 1, colMetaData)
+  test.strictEqual(token.length, buffer.length - 1)
+  test.strictEqual(token.columns.length, 11)
+  test.strictEqual("0", token.columns[0].value)
+  test.strictEqual("1", token.columns[1].value)
+  test.strictEqual("-1", token.columns[2].value)
+  test.strictEqual("2", token.columns[3].value)
+  test.strictEqual("-2", token.columns[4].value)
+  test.strictEqual("9223372036854775807", token.columns[5].value)
+  test.strictEqual("-9223372036854775808", token.columns[6].value)
+  test.strictEqual("10", token.columns[7].value)
+  test.strictEqual("100", token.columns[8].value)
+  test.strictEqual("1000", token.columns[9].value)
+  test.strictEqual("10000", token.columns[10].value)
+
+  test.done()
+
+
 module.exports.int = (test) ->
   colMetaData = [type: dataTypeByName.Int]

@@ -72,22 +124,55 @@
   test.done()

 module.exports.intN = (test) ->
-  colMetaData = [type: dataTypeByName.IntN]
+  colMetaData = [{type: dataTypeByName.IntN},
+                 {type: dataTypeByName.IntN},
+                 {type: dataTypeByName.IntN},
+                 {type: dataTypeByName.IntN},
+                 {type: dataTypeByName.IntN},
+                 {type: dataTypeByName.IntN},
+                 {type: dataTypeByName.IntN},
+                 {type: dataTypeByName.IntN},
+                 {type: dataTypeByName.IntN},
+                 {type: dataTypeByName.IntN},
+                 {type: dataTypeByName.IntN},
+                 {type: dataTypeByName.IntN}]

   buffer = new WritableBuffer(0)

   buffer.writeUInt8(TYPE.ROW)
   buffer.writeUInt8(0)
-  buffer = buffer.data
-  #console.log(buffer)
-
-  token = parser(buffer, 1, colMetaData)
-  #console.log(token)
-
-  test.strictEqual(token.length, buffer.length - 1)
-  test.strictEqual(token.columns.length, 1)
+  writeBytes( buffer, [8, 0,0,0,0,0,0,0,0,
+                        8, 1,0,0,0,0,0,0,0,
+                        8, 255,255,255,255,255,255,255,255,
+                        8, 2,0,0,0,0,0,0,0,
+                        8, 254,255,255,255,255,255,255,255,
+                        8, 255,255,255,255,255,255,255,127,
+                        8, 0,0,0,0,0,0,0,128,
+                        8, 10,0,0,0,0,0,0,0,
+                        8, 100,0,0,0,0,0,0,0,
+                        8, 232,3,0,0,0,0,0,0,
+                        8, 16,39,0,0,0,0,0,0] )
+  buffer = buffer.data
+  #console.log(buffer)
+
+  token = parser(buffer, 1, colMetaData)
+  #console.log(token)
+
+  test.strictEqual(token.length, buffer.length - 1)
+  test.strictEqual(token.columns.length, 12)
   test.ok(!token.columns[0].value)
   test.ok(token.columns[0].isNull)
+  test.strictEqual("0", token.columns[1].value)
+  test.strictEqual("1", token.columns[2].value)
+  test.strictEqual("-1", token.columns[3].value)
+  test.strictEqual("2", token.columns[4].value)
+  test.strictEqual("-2", token.columns[5].value)
+  test.strictEqual("9223372036854775807", token.columns[6].value)
+  test.strictEqual("-9223372036854775808", token.columns[7].value)
+  test.strictEqual("10", token.columns[8].value)
+  test.strictEqual("100", token.columns[9].value)
+  test.strictEqual("1000", token.columns[10].value)
+  test.strictEqual("10000", token.columns[11].value)

   test.done()


StackTemplateList: Move all back-end logic to controller

--- client/app/lib/stacks/stacktemplatelist.coffee
+++ client/app/lib/stacks/stacktemplatelist.coffee
@@ -19,38 +19,28 @@
     super options, data

-  deleteItem: (item) ->
+  askForEdit: (options) ->

-    template = item.getData()
+    { callback } = options

-    currentGroup = kd.singletons.groupsController.getCurrentGroup()
-    if template._id in (currentGroup.stackTemplates ? [])
-      return showError 'This template currently in use by the Team'
-
-    if kd.singletons.computeController.findStackFromTemplateId template._id
-      return showError 'You currently have a stack generated from this template'
-
-    # Since KDModalView.confirm not passing overlay options
-    # to the base class (KDModalView) I had to do this hack
-    # Remove this when issue fixed in Framework ~ GG
-    overlay = new KDOverlayView { cssClass: 'second-overlay' }
-
-    modal   = KDModalView.confirm
-      title       : 'Remove stack template ?'
-      description : 'Do you want to remove this stack template ?'
-      ok          :
-        title     : 'Yes'
-        callback  :  => template.delete (err) =>
-          modal.destroy()
-          @emit 'ItemDeleted', item  unless showError err
-          Tracker.track Tracker.STACKS_DELETE_TEMPLATE
-
-    modal.once   'KDObjectWillBeDestroyed', overlay.bound 'destroy'
-    overlay.once 'click',                   modal.bound   'destroy'
-
-    return modal
-
-
-  showItemContent: (item) ->
-
-    new StackTemplateContentModal {}, item.getData()
+    modal = new KDModalView
+      title          : 'Editing default stack template ?'
+      overlay        : yes
+      overlayOptions :
+        cssClass     : 'second-overlay'
+        overlayClick : yes
+      content        : '
+        This stack template is currently used by your team. If you continue
+        to edit, all of your changes will be applied to all team members directly.
+        We highly recommend you to clone this stack template
+        first and work on the cloned version. Once you finish your work,
+        you can easily apply your changes for all team members.
+      '
+      buttons      :
+        'Clone and Open Editor':
+          style    : 'solid medium green'
+          loader   : yes
+          callback : -> callback { action : 'CloseAndOpen', modal }
+        "I know what I'm doing, Open Editor":
+          style    : 'solid medium red'
+          callback : -> callback { action : 'OpenEditor', modal }


Add comment

Add comment

--- client/landing/app/MainApp/fs/fswatcher.coffee
+++ client/landing/app/MainApp/fs/fswatcher.coffee
@@ -9,6 +9,7 @@
     (watcher.stop() for path, watcher of @watchers)
     @watchers = {}

+  # /parentPath/also/stops/childrenPaths
   @stopWatching:(pathToStop)->
     for path, watcher of @watchers  when (path.indexOf pathToStop) is 0
       watcher.stop()


Other small fixes

Other small fixes

--- client/landing/app/MainApp/kite/newkite.coffee
+++ client/landing/app/MainApp/kite/newkite.coffee
@@ -60,20 +60,9 @@
     try
       args = JSON.parse evt.data
       {method} = args
+      console.log "received", {args}
       callback = @localStore.get method
       callback.apply this, @unscrub args
-      # data = JSON.parse evt.data
-      # {id} = data
-      # if id
-      #   {result, error} = data
-      #   id = "#{id}"
-      #   @localStore[id].call null, error, result
-      #   delete @localStore[id]
-      # else
-      #   {method, params} = data
-      #   console.log "method is", method, params[0], @localMethods[method].toString()
-      #   @localMethods[method].call null, params[0]
-
     catch e
       console.log "error: ", e, evt.data

@@ -95,7 +84,7 @@
     console.log {method}, {args}
     @scrub method, args, (scrubbed) =>
       messageString = JSON.stringify(scrubbed)
-      console.log {messageString}
+      console.log "Sending", {messageString}
       @ready => @websocket.send messageString

   scrub: (method, args, callback) ->


added environments route

added environments route

--- client/landing/app/MainApp/kodingrouter.coffee
+++ client/landing/app/MainApp/kodingrouter.coffee
@@ -230,6 +230,7 @@
       '/:name?/Account'                 : createSectionHandler 'Account'
       '/:name?/Demos'                   : createSectionHandler 'Demos'
       '/:name?/Dashboard'               : createSectionHandler 'Dashboard'
+      '/:name?/Environments'            : createSectionHandler 'Environments'

       # group dashboard
       # '/:name?/Dashboard'               : (routeInfo, state, route)->


update service fields for auxilary services

--- imports/server/stateReconciler.coffee
+++ imports/server/stateReconciler.coffee
@@ -64,7 +64,8 @@
 setServiceField = (fieldName, fieldValue, labels) ->
   if (name = labels['bigboat.instance.name']) and (service = labels['bigboat.service.name']) and labels['bigboat.service.type'] in ['service', 'oneoff']
     Instances.upsert {name: name}, $set: {"services.#{service}.#{fieldName}": fieldValue}
-
+  if (name = labels['bigboat.instance.name']) and (service = labels['bigboat.service.name']) and (type = labels['bigboat.service.type']) in ['net', 'ssh']
+    Instances.upsert {name: name}, $set: {"services.#{service}.aux.#{type}.#{fieldName}": fieldValue}
 labelsToObject = (labels) ->
   obj = {}
   for label, val of labels


FPS limiting implemented.

FPS limiting implemented.

--- coffee/hyperbolic_tessellation_test_app.coffee
+++ coffee/hyperbolic_tessellation_test_app.coffee
@@ -256,11 +256,17 @@
   context.translate 1, 1
   observer.draw cells, tfm, context
   context.restore()
-
+
+lastTime = Date.now()
+fpsMax = 10
+dtMax = 1000.0/fpsMax
 redrawLoop = ->
   if dirty
-    drawEverything()
-    dirty = false
+    t = Date.now()
+    if t - lastTime > dtMax
+      drawEverything()
+      dirty = false
+      lastTime = t
   requestAnimationFrame redrawLoop


Consistent treatment of search terms; fix tests.

--- tests/unit_tests/utils_test.coffee
+++ tests/unit_tests/utils_test.coffee
@@ -45,7 +45,8 @@

   should "convert non-URL terms into search queries", ->
     assert.equal "http://www.google.com/search?q=google", Utils.convertToUrl("google")
-    assert.equal "http://www.google.com/search?q=go%20ogle.com", Utils.convertToUrl("go ogle.com")
+    assert.equal "http://www.google.com/search?q=go+ogle.com", Utils.convertToUrl("go ogle.com")
+    assert.equal "http://www.google.com/search?q=%40twitter", Utils.convertToUrl("@twitter")

 context "hasChromePrefix",
   should "detect chrome prefixes of URLs", ->


Favor sign in with learn

Favor sign in with learn

--- backend/ide-init.coffee
+++ backend/ide-init.coffee
@@ -1,6 +1,7 @@
 url = require 'url'
 https = require 'https'
 remote = require 'remote'
+shell = require 'shell'
 BrowserWindow = remote.require('browser-window')

 workspaceView = atom.views.getView(atom.workspace)
@@ -65,6 +66,35 @@

   promptManualEntry() unless win.loadUrl('https://learn.co/ide/token?ide_config=true')

+window.learnSignIn = ->
+  win = new BrowserWindow(show: false, width: 400, height: 600)
+  {webContents} = win
+
+  win.setSkipTaskbar(true)
+  win.setMenuBarVisibility(false)
+  win.setTitle('Sign in to Github to get started with the Learn IDE')
+
+  webContents.on 'did-finish-load', -> win.show()
+
+  webContents.on 'new-window', (e, url) ->
+    e.preventDefault()
+    win.destroy()
+    shell.openExternal(url)
+
+  webContents.on 'did-get-redirect-request', (e, oldURL, newURL) ->
+    if newURL.match(/ide_token/)
+      token = url.parse(newURL, true).query.ide_token
+      confirmOauthToken(token).then (res) ->
+        if res
+          atom.config.set('integrated-learn-environment.oauthToken', token)
+          atom.config.set('integrated-learn-environment.vm_port', res.vm_uid)
+          atom.commands.dispatch(workspaceView, 'learn-ide:toggle-terminal')
+    if newURL.match(/rollout_failed/)
+      win.destroy()
+      getTokenAndVMPort()
+
+  win.loadUrl('http://localhost:3000/ide/sign_in')
+
 promptManualEntry = ->
   oauthPrompt = document.createElement 'div'
   oauthPrompt.setAttribute 'style', 'width:100%; text-align: center;'
@@ -116,17 +146,30 @@
       atom.commands.dispatch(workspaceView, 'learn-ide:toggle-terminal')
       return true

+window.BrowserWindow = BrowserWindow
+
 # TODO: Remove this temporary helper eventually...
 window.githubLogout = ->
   win = new BrowserWindow(show: false)
   win.webContents.on 'did-finish-load', -> win.show()
   win.loadUrl('https://github.com/logout')

+window.learnLogout = ->
+  win = new BrowserWindow(show: false)
+  win.webContents.on 'did-finish-load', -> win.destroy()
+  win.loadUrl('http://localhost:3000/sign_out')
+
+window.logout = ->
+  atom.config.unset('integrated-learn-environment.oauthToken')
+  atom.config.unset('integrated-learn-environment.vm_port')
+  learnLogout()
+  githubLogout()
+
 existingToken = atom.config.get('integrated-learn-environment.oauthToken')
 vmPort = atom.config.get('integrated-learn-environment.vm_port')

 if !existingToken
-  getTokenAndVMPort()
+  learnSignIn()
 else if !vmPort
   getVMPort()
 else


Autocomplete replaceUpdateDescription in its own function

--- lib/elixir-autocomplete-provider.coffee
+++ lib/elixir-autocomplete-provider.coffee
@@ -28,40 +28,7 @@

     # This is a hack until descriptionHTML is suported. See:
     # https://github.com/atom/autocomplete-plus/issues/423
-
-    SuggestionList = require "#{atom.packages.getActivePackage('autocomplete-plus').path}/lib/suggestion-list"
-    SuggestionListElement = require "#{atom.packages.getActivePackage('autocomplete-plus').path}/lib/suggestion-list-element"
-    autocompleteManager = atom.packages.getActivePackage('autocomplete-plus').mainModule.autocompleteManager
-    viewProvider = (p for p in atom.views.providers when p.modelConstructor.name is 'SuggestionList')[0]
-
-    viewProvider.createView = (model) ->
-      element = new SuggestionListElement().initialize(model)
-      element.updateDescription = (item) ->
-        suggestionList = atom.packages.getActivePackage('autocomplete-plus').mainModule.autocompleteManager.suggestionList
-        suggestionListView = atom.views.getView(suggestionList)
-        descriptionContent = suggestionListView.querySelector('.suggestion-description-content')
-        descriptionContainer = suggestionListView.querySelector('.suggestion-description')
-        descriptionMoreLink = suggestionListView.querySelector('.suggestion-description-more-link')
-
-        #TODO: remove this
-        descriptionMoreLink.style.display = 'none'
-
-        item = item ? @model?.items?[@selectedIndex]
-        return unless item?
-        if item.descriptionHTML?
-          descriptionContainer.style.display = 'block'
-          descriptionContent.innerHTML = item.descriptionHTML
-          # if item.descriptionMoreURL? and item.descriptionMoreURL.length?
-          #   descriptionMoreLink.style.display = 'inline'
-          #   descriptionMoreLink.setAttribute('href', item.descriptionMoreURL)
-          # else
-          #   descriptionMoreLink.style.display = 'none'
-        else if item.description? and item.description.length > 0
-          descriptionContainer.style.display = 'block'
-          descriptionContent.textContent = item.descriptionHTML
-        else
-          @descriptionContainer.style.display = 'none'
-      element
+    replaceUpdateDescription()

   getSuggestions: ({editor, bufferPosition, scopeDescriptor, prefix}) ->
     prefix = getPrefix(editor, bufferPosition)
@@ -234,3 +201,31 @@
       smartypants: false
     });
     marked(mdText)
+
+  replaceUpdateDescription = ->
+    SuggestionList = require "#{atom.packages.getActivePackage('autocomplete-plus').path}/lib/suggestion-list"
+    SuggestionListElement = require "#{atom.packages.getActivePackage('autocomplete-plus').path}/lib/suggestion-list-element"
+    autocompleteManager = atom.packages.getActivePackage('autocomplete-plus').mainModule.autocompleteManager
+    viewProvider = (p for p in atom.views.providers when p.modelConstructor.name is 'SuggestionList')[0]
+
+    viewProvider.createView = (model) ->
+      element = new SuggestionListElement().initialize(model)
+      element.updateDescription = (item) ->
+        suggestionList = atom.packages.getActivePackage('autocomplete-plus').mainModule.autocompleteManager.suggestionList
+        suggestionListView = atom.views.getView(suggestionList)
+        descriptionContent = suggestionListView.querySelector('.suggestion-description-content')
+        descriptionContainer = suggestionListView.querySelector('.suggestion-description')
+        descriptionMoreLink = suggestionListView.querySelector('.suggestion-description-more-link')
+
+        descriptionMoreLink.style.display = 'none'
+        item = item ? @model?.items?[@selectedIndex]
+        return unless item?
+        if item.descriptionHTML?
+          descriptionContainer.style.display = 'block'
+          descriptionContent.innerHTML = item.descriptionHTML
+        else if item.description? and item.description.length > 0
+          descriptionContainer.style.display = 'block'
+          descriptionContent.textContent = item.descriptionHTML
+        else
+          @descriptionContainer.style.display = 'none'
+      element


test(ipa): update invalid dns error

test(ipa): update invalid dns error

--- packages/ipa/env/ipa/start.coffee
+++ packages/ipa/env/ipa/start.coffee
@@ -25,8 +25,9 @@
 # Host 'ipa.nikita.local' does not have corresponding DNS A/AAAA record
 # Short term solution:
 # Reset the DNS server in resolv.conf with the IPA DNS
-# echo 'search nikita.local' > /etc/resolv.conf
-# echo 'nameserver 10.10.11.2' > /etc/resolv.conf
+#     echo 'search nikita.local' > /etc/resolv.conf
+#     echo 'nameserver 10.10.11.2' >> /etc/resolv.conf
+#     ipactl restart
 # Long term solution:
 # Disable the re-generation of resolv.conf by /usr/sbin/dhclient-script


Provide a way to store state among middlewares.

--- packages/middleware/server.coffee
+++ packages/middleware/server.coffee
@@ -56,8 +56,16 @@
     Meteor.publish @options.name, ->
       publish = @

+      state = {}
+
       publish.params = ->
         @_params
+
+      publish.set = (key, value) ->
+        state[key] = value
+
+      publish.get = (key) ->
+        state[key]

       self.publish self.middlewares, publish


use promises correctly

use promises correctly

--- js/src/http/UserDirectoryApi.coffee
+++ js/src/http/UserDirectoryApi.coffee
@@ -37,8 +37,9 @@
   class UserDirectoryApi

     getUserName: ( uuid ) =>
-      user = @getUser( uuid )
-      return user.name
+      @getUser( uuid )
+      .then (user) ->
+        return user.name

     resolve: ({ email }) ->
       Promise.resolve()
@@ -57,10 +58,11 @@
           return uuid

     getUser: (uuid) ->
-      cached = Cache.get( Cache.USERS, uuid )
-      if cached?
-        return cached
-      Promise.resolve()
+      return Promise.resolve()
+      .then ->
+        cached = Cache.get( Cache.USERS, uuid )
+        if cached?
+          return cached
       .then ->
         validateUuid(uuid)
         axios({
@@ -82,9 +84,9 @@
         for uuid in uuids
           validateUuid(uuid)
         axios({
-          url    : getUsersUrl() + '/'
-          body   : uuids
-          method : 'GET'
+          url    : getUsersUrl()
+          method : 'POST'
+          data   : uuids
         })
       .then (response) ->
         users = response.data


fix the calendar, interesting, I did not know this would make a difference?

--- src/components/course-calendar/plan.cjsx
+++ src/components/course-calendar/plan.cjsx
@@ -164,8 +164,8 @@
     labelProps = {rangeDuration, plan, index, offset, offsetFromPlanStart}
     label = <CoursePlanLabel {...labelProps} ref="label#{index}"/>

-    displayComponent = CoursePlanDisplayEdit
-    displayComponent = CoursePlanDisplayQuickLook if hasQuickLook
+    DisplayComponent = CoursePlanDisplayEdit
+    DisplayComponent = CoursePlanDisplayQuickLook if hasQuickLook

     displayComponentProps = {
       plan,
@@ -179,7 +179,7 @@
       setIsViewing: @setIsViewing
     }

-    <displayComponent
+    <DisplayComponent
       {...displayComponentProps}
       ref="display#{index}"
       key="display#{index}"/>


Add help links for students

Add help links for students

--- src/components/course-listing/empty.cjsx
+++ src/components/course-listing/empty.cjsx
@@ -4,9 +4,9 @@

 FAQ_BASE = 'http://openstax.force.com/support/articles/FAQ/'

-CC_STUDENT_HELP = ''
-CC_FACULTY_HELP = 'Getting-Started-with-Concept-Coach/?q=getting+started&l=en_US&c=Products%3AConcept_Coach&fs=Search&pn=1'
-TUTOR_STUDENT_HELP = ''
+CC_STUDENT_HELP = 'Getting-Started-with-Concept-Coach-Guide-Students/?q=getting+started&l=en_US&c=Products%3AConcept_Coach&fs=Search&pn=1'
+CC_FACULTY_HELP = 'Getting-Started-with-Concept-Coach-Guide-Teachers/?q=getting+started&l=en_US&c=Products%3AConcept_Coach&fs=Search&pn=1'
+TUTOR_STUDENT_HELP = 'Getting-Started-with-Tutor-Guide-Students/?q=getting+started&l=en_US&c=Products%3ATutor&fs=Search&pn=1'

 EmptyCourses = React.createClass


fix typo

fix typo

--- lib/HtmlReport.coffee
+++ lib/HtmlReport.coffee
@@ -19,7 +19,7 @@
         #{@resultSummaryCount 'total',    report.counts[0] + report.counts[1] + report.counts[2]}
         #{@resultSummaryCount 'passed',   report.counts[0]}
         #{@resultSummaryCount 'pending',  report.counts[1]}
-        #{@resultSummaryCount 'failed',   report.counts[1]}
+        #{@resultSummaryCount 'failed',   report.counts[2]}
       </div>
     "


timeout

timeout

--- src/6.web.coffee
+++ src/6.web.coffee
@@ -59,7 +59,7 @@
             urlMod = module.require("url")
             parsedUrl = urlMod.parse(url)
             httpOrHttps = if parsedUrl.protocol == "https:" then https else http
-            httpOrHttps.request(
+            rawRequest = httpOrHttps.request(
                 {
                     method: method
                     hostname: parsedUrl.hostname
@@ -91,8 +91,14 @@
                         else
                             reject(response)
                     )
-            ).on("error", (e) ->
-                fail()
+            )
+            if timeout?
+                rawRequest.setTimeout(timeout, ->
+                    rawRequest.abort()
+                    reject(new Error("timeout"))
+                )
+            rawRequest.on("error", (e) ->
+                reject(new Error("error"))
             ).end()
     )
 web.get = (url, options) ->


a more decent solution to #399 (5 new failures)

--- src/Bacon.coffee
+++ src/Bacon.coffee
@@ -1345,7 +1345,6 @@
   rootEvent = undefined
   waiters = []
   afters = []
-  processingAfters = false
   afterTransaction = (f) ->
     if rootEvent
       afters.push(f)
@@ -1383,14 +1382,11 @@
         flush()
       finally
         rootEvent = undefined
-        if not processingAfters
-          processingAfters = true
-          try
-            while (afters.length)
-              f = afters.shift()
-              f()
-          finally
-            processingAfters = false
+        while (afters.length)
+          theseAfters = afters
+          afters = []
+          for f in theseAfters
+            f()
         invalidateDeps()
       result


suites that contain no components now have a status of pending

--- lib/Spec/Suite.coffee
+++ lib/Spec/Suite.coffee
@@ -8,6 +8,7 @@
     if @definition
       window.__spec_definingSuite = this
       @definition()
+
       delete window.__spec_definingSuite
     @loaded = true

@@ -21,6 +22,7 @@
     @load() unless @loaded
     allFilters = filters.concat(@filters)
     report = new Spec.Report(@title)
+    report.status = Spec.Report.Pending unless @components.length

     for component in @components
       report.addSubreport component.run(allFilters)


Reenabling IP reports from VT

Reenabling IP reports from VT

--- src/scripts/virustotal.coffee
+++ src/scripts/virustotal.coffee
@@ -10,6 +10,7 @@
 # Commands:
 #   hubot virustotal hash <hash> - Searches VirusTotal for a hash
 #   hubot virustotal url <url> - Searches VirusTotal for a url
+#   hubot virustotal ip <ip> - Searches VirusTotal for a ip address
 #
 # Author:
 #   Scott J Roberts - @sroberts
@@ -81,32 +82,36 @@
     else
       msg.send "VirusTotal API key not configured. Get one at https://www.virustotal.com/en/user/ in the API tab"

-  #   hubot virustotal ip <hash> - Searches VirusTotal for a ip address
-  #robot.respond /virustotal ip (.*)/i, (msg) ->
-    #ip = msg.match[1].toLowerCase()
+  robot.respond /virustotal ip (.*)/i, (msg) ->
+    ip = msg.match[1].toLowerCase()

-    #robot.http(vt_ip_report_url)
-      #.query("apikey": VIRUS_TOTAL_API_KEY, "ip": ip)
-      #.get() (err, res, body) ->
-        #vt_json = JSON.parse(body)
+    robot.http(vt_ip_report_url)
+      .query("apikey": VIRUSTOTAL_API_KEY, "ip": ip)
+      .get() (err, res, body) ->
+        if res.statusCode is 200
+          vt_json = JSON.parse(body)

-        #if vt_json.response_code == 1
+          if vt_json.response_code == 1

-          #console.log(vt_json.resolutions)
-          # for resolution in vt_json.resolutions
-          #   console.log("#{resolution}")
+            resolutions = ""

-          #console.log(vt_json.detected_urls)
-          # for detected_url in vt_json.detected_urls
-          #   console.log("#{detected_url}")
+            for resolution in vt_json.detected_urls

-          # summary = """ VirusTotal IP Result: #{ip}
-          # - Scanned at: #{vt_json.scan_date}
-          # - Results:    #{vt_json.positives}/#{vt_json.total}
-          # - Link:       #{vt_json.permalink}
-          # """

-          #msg.send summary
+              resolutions += "- #{resolution.url}\n
+              \t- Last Scan: #{resolution.scan_date}\n
+              \t- Malicious: #{resolution.positives} / #{resolution.total} (#{(resolution.positives / resolution.total) * 100}%)\n
+              "

-        #else
-          #msg.send vt_json.verbose_msg
+            summary = """ VirusTotal IP Result: #{ip}
+            - Detected Communicating Samples: #{vt_json.detected_communicating_samples.length}
+
+            #{resolutions}
+            """
+
+            msg.send summary
+          else
+            msg.send "Error: Couldn't access #{vt_url}."
+
+        else
+          msg.send vt_json.verbose_msg


Create ControllablesMixin

Create ControllablesMixin

--- src/index.coffee
+++ src/index.coffee
@@ -1,6 +1,33 @@
 # react-controllables
 # ===================

-ControllablesMixin = {}
+invariant = require 'react/lib/invariant'
+
+
+getControllableValue = (name, state, props) -> props[name] ? state[name]
+capFirst = (str) -> "#{ str.charAt(0).toUpperCase() }#{ str[1...] }"
+
+
+ControllablesMixin =
+  getInitialState: ->
+    invariant(
+      !!@controllables,
+      'Components that use ControllablesMixin must define a controllables array'
+    )
+    state = {}
+    for name in @controllables
+      defaultValue = @props["default#{ capFirst name }"]
+      if defaultValue? then state[name] = defaultValue
+    state
+  getControllableValue: (name) -> getControllableValue name, @state, @props
+  componentDidUpdate: (prevProps, prevState) ->
+    for name in @controllables
+      newValue = @state[name]
+      oldValue = getControllableValue name, prevState, prevProps
+      if newValue isnt oldValue
+        cb = "on#{ capFirst name }Change"
+        @props[cb]? newValue, oldValue
+    return
+

 module.exports = ControllablesMixin


fixed nasty bug with ambiguous increment

--- lib/image/jpeg.coffee
+++ lib/image/jpeg.coffee
@@ -12,17 +12,19 @@
         len = data.length

         if data.readUInt16() isnt 0xFFD8
-            throw "SOI not found in JPEG"
+            throw new Error "SOI not found in JPEG"

         markers = [0xFFC0, 0xFFC1, 0xFFC2, 0xFFC3, 0xFFC4, 0xFFC5, 0xFFC6, 0xFFC7,
                    0xFFC8, 0xFFC9, 0xFFCA, 0xFFCB, 0xFFCC, 0xFFCD, 0xFFCE, 0xFFCF]
-
+
         while data.pos < len
             marker = data.readUInt16()
             break if marker in markers
-            data.pos += data.readUInt16()
-
-        throw "Invalid JPEG." unless marker in markers
+            # was ambiguous: data.pos += data.readUInt16()
+            pos = data.pos
+            data.pos = pos + data.readUInt16()
+
+        throw new Error "Invalid JPEG." unless marker in markers
         data.pos += 2

         @bits = data.readByte()


Fix a JPEG parsing issue with Progressive DCT

--- lib/image/jpeg.coffee
+++ lib/image/jpeg.coffee
@@ -9,14 +9,21 @@

     constructor: (@data) ->
         len = data.length
-        data.pos = 2 # Skip the first two bytes of JPEG identifier.
+
+        if data.readUInt16() isnt 0xFFD8
+            throw "SOI not found in JPEG"
+
+        markers = [0xFFC0, 0xFFC1, 0xFFC2, 0xFFC3, 0xFFC4, 0xFFC5, 0xFFC6, 0xFFC7,
+                   0xFFC8, 0xFFC9, 0xFFCA, 0xFFCB, 0xFFCC, 0xFFCD, 0xFFCE, 0xFFCF]

         while data.pos < len
             marker = data.readUInt16()
-            break if marker is 0xFFC0
+            break if marker in markers
             data.pos += data.readUInt16()

-        data.pos += 2
+        throw "Invalid JPEG." unless marker in markers
+        data.pos += 2
+
         @bits = data.readByte()
         @height = data.readShort()
         @width = data.readShort()


Tweak

Tweak

--- lib/json_utils.coffee
+++ lib/json_utils.coffee
@@ -160,6 +160,7 @@
   # Render a single key friom a model to json: model.get('key')
   @renderJSONKey = (model, key, options, callback) ->
     (callback = options; options = {}) if arguments.length is 3
+
     model.get key, (err, value) ->
       return callback(err) if err
       # Related models need to be converted to json


Hotfix: Setting debug flag to false by default

--- src/mixer.coffee
+++ src/mixer.coffee
@@ -3,6 +3,9 @@
   ownProp = Object.hasOwnProperty

   mixinList = {}
+
+  # DEBUG is set to false by default
+  DEBUG = false

   # Expose mixer list if debug
   root.mixinList = mixinList if DEBUG


Prefill new post textarea on profile pages with an inline mention

--- lib/assets/javascripts/views/post/002_new_post_form.js.coffee
+++ lib/assets/javascripts/views/post/002_new_post_form.js.coffee
@@ -67,6 +67,18 @@
     @initHotkeys()

     Marbles.DOM.on(@elements.form, 'submit', @submitWithValidation)
+
+    if @parentView().constructor.view_name == 'profile'
+      profile = @parentView().profile()
+      unless profile.get('entity') == TentStatus.config.meta.content.entity
+        textarea_view = @textareaMentionsView()
+
+        inline_mention = new TentStatus.InlineMentionsManager.InlineMention(
+          entity: profile.get('entity')
+          display_text: profile.get('name') || TentStatus.Helpers.minimalEntity(profile.get('entity'))
+        )
+
+        textarea_view.el.value = inline_mention.toExpandedMarkdownString() + " "

   initHotkeys: =>
     ## cmd/ctr enter to submit


Fixed issue with remove callback eating up parts of a line

--- lib/ecrire/app/assets/javascripts/admin/editor/content.coffee
+++ lib/ecrire/app/assets/javascripts/admin/editor/content.coffee
@@ -111,7 +111,7 @@
     while line? && line.parentElement != @element()
       line = line.parentElement

-    if node.nodeType != 1 || (node instanceof HTMLBRElement && node.parentElement?)
+    if node.nodeType != 1 && node instanceof HTMLBRElement
       node.remove()

     if line?


Encapsulating modules and import/export

Encapsulating modules and import/export

--- src/js/app/compilers/teascript/compiler.coffee
+++ src/js/app/compilers/teascript/compiler.coffee
@@ -1309,6 +1309,16 @@
   else
     throw new Error "Missing definition at top level"

+topLevelModule = (moduleName, defaultImports) -> (ctx, form) ->
+  [jsVarDeclaration (validIdentifier moduleName),
+    (exportAll ctx, (join (importAny defaultImports), (topLevel ctx, form)))]
+
+topLevelExpressionInModule = (defaultImports) -> (ctx, expression) ->
+  (iife (concat [
+    toJsString 'use strict'
+    (importAny defaultImports)
+    [(jsReturn (topLevelExpression ctx, expression))]]))
+
 definitionList = (ctx, pairs) ->
   # log "yay"
   concat (definitionListCompile ctx, pairs)
@@ -1423,6 +1433,19 @@
     undefined
   else
     compiled
+
+importAny = (defaultImports) ->
+  concat (for name, names of values defaultImports
+    imports name, names)
+
+exportAll = (ctx, definitions) ->
+  nonVirtual = filterMap ((name, declaration) -> not declaration.virtual), ctx._scope()
+  exported = subtractSets nonVirtual, builtInDefinitions()
+  exportList = map validIdentifier, (setToArray exported)
+  (iife (concat [
+    toJsString 'use strict'
+    definitions
+    [(jsReturn (jsDictionary exportList, exportList))]]))

 ms = {}
 ms.fn = ms_fn = (ctx, call) ->
@@ -1819,7 +1842,7 @@
   for name, {arity, type} of values methodDeclarations
     type = quantifyUnbound ctx,
       (addConstraints (freshInstance ctx, type), [classConstraint])
-    ctx.declare name, {arity, type}
+    ctx.declare name, {arity, type, virtual: yes}
   return

 ms.instance = ms_instance = (ctx, call) ->
@@ -1886,6 +1909,8 @@
       if not all methodTypes
         malformed ctx, call, "missing type of a method"
         return jsNoop()
+
+      ctx.declare instanceName, virtual: no

       # """var #{instanceName} = new #{className}(#{listOf methods});"""
       (jsVarDeclaration (validIdentifier instanceName),
@@ -2011,8 +2036,15 @@
   if not moduleName or not isName moduleName
     return malformed ctx, call, 'req requires a module name to require from'
   moduleName.label = 'param'
-  ctx.req moduleName.symbol, (arrayToSet (filter _is, (map _symbol, reqs)))
-  jsNoop()
+  requiredNames = (arrayToSet (filter _is, (map _symbol, reqs)))
+  ctx.req moduleName.symbol, requiredNames
+  imports moduleName.symbol, setToArray requiredNames
+
+imports = (moduleName, names) ->
+  validModuleName = validIdentifier moduleName
+  for name in names
+    validName = validIdentifier name
+    jsVarDeclaration validName, (jsAccess validModuleName, validName)

 ms.format = ms_format = (ctx, call) ->
   typeTable =
@@ -2503,7 +2535,7 @@
     params: (map validIdentifier, allFieldNames)
     body: paramAssigns)
   accessors = fieldNames.map (name) ->
-    (jsAssignStatement (validIdentifier "#{dictName}-#{name}"), (jsFunction
+    (jsVarDeclaration (validIdentifier "#{dictName}-#{name}"), (jsFunction
       name: (validIdentifier name)
       params: ["dict"]
       body: [(jsReturn (jsAccess "dict", name))]))
@@ -3205,6 +3237,12 @@
         #{elseCase}
       }"""

+jsDictionary = (keys, values) ->
+  {js: jsDictionaryTranslate, keys, values}
+
+jsDictionaryTranslate = ({keys, values}) ->
+  body = zipWith ((key, value) -> "\"#{key}\": #{value}"), keys, values
+  "{#{listOf body}}"

 jsExprList = (elems) ->
   {js: jsExprListTranslate, elems}
@@ -3382,13 +3420,13 @@

 # for including in other files
 # TODO: support with arbitrary left patterns, prob via context
-exportList = (source) ->
-  wheres = whereList inside preCompileDefs source
-  names = []
-  for [pattern] in wheres
-    if pattern.symbol and pattern.symbol isnt '_'
-      names.push pattern.symbol
-  names
+# exportList = (source) ->
+#   wheres = whereList inside preCompileDefs source
+#   names = []
+#   for [pattern] in wheres
+#     if pattern.symbol and pattern.symbol isnt '_'
+#       names.push pattern.symbol
+#   names

 # Valid identifiers

@@ -3987,6 +4025,12 @@
   initialized = newMap()
   for key, val of set.values
     addToMap initialized, key, fn val
+  initialized
+
+mapKeys = (fn, map) ->
+  initialized = newMap()
+  for key, val of map.values
+    addToMap initialized, key, fn key
   initialized

 mapSet =
@@ -4852,7 +4896,9 @@
   replaceOrAddToMap moduleGraph, moduleName, requires: requiredMap
   toInject = collectRequiresFor moduleName
   ctx = injectedContext toInject
-  {request, ast, ir, js} = compileCtxAstToJs topLevel, ctx, (astFromSource "(#{source})", -1, -1)
+  defaultImports = (subtractSets (newSetWith 'Prelude'), (newSetWith moduleName))
+  compilationFn = (topLevelModule moduleName, importsFor defaultImports)
+  {request, ast, ir, js} = compileCtxAstToJs compilationFn, ctx, (astFromSource "(#{source})", -1, -1)
   if request
     if not allInjected request, requiredMap
       return compileTopLevel source, moduleName, request
@@ -4869,7 +4915,7 @@

 compileExpression = (source, moduleName = '@unnamed') ->
   ast = (astFromSource "(#{source})", -1, -1)
-  if _empty _terms ast
+  if _empty _validTerms ast
     {
       ast: ast
       js: ''
@@ -4878,16 +4924,26 @@
     module = lookupCompiledModule moduleName
     {modules, ctx} = contextWithDependencies moduleName
     [expression] = _terms ast
-    {js} = compileCtxAstToJs topLevelExpression, ctx, expression
+    compilationFn = (topLevelExpressionInModule importsFor moduleDependencies moduleName)
+    {js} = compileCtxAstToJs compilationFn, ctx, expression
     (finalizeTypes ctx, expression)
-    js: library + immutable + (listOfLines map lookupJs, (reverse modules)) + '\n;' + js
+    js: library + immutable + (listOfLines map lookupJs, modules) + '\n;' + js
     ast: ast
     errors: checkTypes ctx

+importsFor = (moduleSet) ->
+  lookupDefinitions = (name) ->
+    console.log name
+    setToArray (lookupCompiledModule name).declared.definitions
+  mapKeys lookupDefinitions, moduleSet
+
 contextWithDependencies = (moduleName) ->
-  toInject = concatSets (newSetWith moduleName), (collectRequiresFor moduleName)
+  toInject = moduleDependencies moduleName
   ctx: injectedContext toInject
   modules: setToArray toInject
+
+moduleDependencies = (moduleName) ->
+  concatSets (collectRequiresFor moduleName), (newSetWith moduleName)

 # Primitive type checking for now
 checkTypes = (ctx) ->
@@ -5059,14 +5115,19 @@

 compileAstToJs = (compileFn, ast) ->
   ctx = new Context
-  compileCtxAstToJs compileFn, ctx, ast
+  compileCtxAstToJsAlways compileFn, ctx, ast
+
+compileCtxAstToJsAlways = (compileFn, ctx, ast) ->
+  ir = compileFn ctx, ast
+  {js} = compileCtxIrToJs ctx, ir
+  {ctx, ast, ir, js}

 compileCtxAstToJs = (compileFn, ctx, ast) ->
   ir = compileFn ctx, ast
   if ctx.requested()
     return request: ctx.requested(), ast: ast, ir: ir
   {js} = compileCtxIrToJs ctx, ir
-  {ast, ir, js}
+  {ctx, ast, ir, js}

 compileCtxIrToJs = (ctx, ir) ->
   if ir


Moved from minus_ to __

Moved from minus_ to __

--- src/js/app/compilers/teascript/compiler.coffee
+++ src/js/app/compilers/teascript/compiler.coffee
@@ -384,7 +384,7 @@
     else
       name
         .replace(/\+/g, 'plus_')
-        .replace(/\-/g, 'minus_')
+        .replace(/\-/g, '__')
         .replace(/\*/g, 'times_')
         .replace(/\//g, 'over_')
         .replace(/\√/g, 'sqrt_')
@@ -858,7 +858,7 @@
   return $listSlice(from - 1, n, xs.tail);
 };

-var showminus_list = function (x) {
+var show__list = function (x) {
   var t = [];
   while (x.length != 0) {
     t.push(x.head);
@@ -879,7 +879,7 @@
   return _curry();
 };

-var fromminus_nullable = function (jsValue) {
+var from__nullable = function (jsValue) {
   if (typeof jsValue === "undefined" || jsValue === null) {
     return {'None:': []};
   } else {


Failed attempt at allowing read-string and eval. The problem is that the compiler context sits on the worker, and so I cannot make a synchronous eval. Clojure is much much simpler - it simply uses Javas namespaces and adds functions to them, which means they can be used in macros.

--- src/js/app/compilers/teascript/compiler.coffee
+++ src/js/app/compilers/teascript/compiler.coffee
@@ -2637,6 +2637,20 @@
         compiled = termCompile ctx, term
         (irJsCompatible term.tea, compiled))
       (jsCall "js#{jsMethod[0].toUpperCase()}#{jsMethod[1...]}", compatibles)
+
+ms['parse-shem'] = ms_parse_shem = (ctx, call) ->
+  [string] = _arguments call
+  compiledString = termCompile ctx, string
+  call.tea = toConstrained markOrigin expressionType, call
+  assignCompile ctx, call, (jsMethod 'runtime', 'parseShem', [compiledString])
+
+ms['eval-shem'] = ms_eval_shem = (ctx, call) ->
+  [source] = _arguments call
+  compiledSource = termCompile ctx, source
+  call.tea = toConstrained markOrigin jsType, call
+  fucked = eval translateToJs translateIr ctx, compiledSource
+  compiled = termCompile ctx, fucked
+  assignCompile ctx, call, compiled

 ms['=='] = ms_eq = (ctx, call) ->
     [a, b] = _arguments call
@@ -5572,16 +5586,20 @@
       js: ''
     }
   else
-    module = lookupCompiledModule moduleName
-    {modules, ctx} = contextWithDependencies moduleDependencies moduleName
     [expression] = _terms ast
-    compilationFn = (topLevelExpressionInModule importsFor moduleDependencies moduleName)
-    {js} = compileCtxAstToJs compilationFn, ctx, expression
-    (finalizeTypes ctx, expression)
-    js: library + immutable + (listOfLines map lookupJs, (setToArray runtimeDependencies moduleName)) + '\n;' + js
-    ast: ast
-    errors: checkTypes ctx
-    malformed: ctx.isMalformed
+    result = compileExpressionFromAst expression, moduleName
+    result.ast = ast
+    result
+
+compileExpressionFromAst = (expression, moduleName) ->
+  module = lookupCompiledModule moduleName
+  {modules, ctx} = contextWithDependencies moduleDependencies moduleName
+  compilationFn = (topLevelExpressionInModule importsFor moduleDependencies moduleName)
+  {js} = compileCtxAstToJs compilationFn, ctx, expression
+  (finalizeTypes ctx, expression)
+  js: library + immutable + (listOfLines map lookupJs, (setToArray runtimeDependencies moduleName)) + '\n;' + js
+  errors: checkTypes ctx
+  malformed: ctx.isMalformed

 importsFor = (moduleSet) ->
   lookupDefinitions = (name) ->
@@ -6007,6 +6025,16 @@

 # end of Utils

+# Exported runtime
+runtime =
+  parseShem: (string) ->
+    astize tokenize string
+
+  evalShem: (ast) ->
+    {js} = compileExpressionFromAst ast, moduleName
+    {js} = compileCtxAstToJs topLevelExpression, (new Context), ast
+    eval js
+
 # Unit tests
 test = (testName, teaSource, result) ->
   try
@@ -7359,6 +7387,7 @@
 #   exports"""

 exports.library = library
+exports.runtime = runtime

 exports.originOf = originOf
 exports.sortedArgs = sortedArgs


Inlining params in beta reducible call

Inlining params in beta reducible call

--- src/js/app/compilers/teascript/compiler.coffee
+++ src/js/app/compilers/teascript/compiler.coffee
@@ -818,12 +818,7 @@
     malformed ctx, call, "Too many arguments to #{operator.symbol}"
   extraParamNames = positionalParams[nonLabeledArgs.length..]
   extraParams = map token_, ("_#{n}" for n in extraParamNames)
-  positionalArgs = map id, nonLabeledArgs # copy
-  extraArgs = map id, extraParams
-  argsInOrder = (for param in paramNames
-    (lookupInMap labeledArgs, param) or
-      positionalArgs.shift() or
-      extraArgs.shift())
+  argsInOrder = sortCallArguments paramNames, labeledArgs, nonLabeledArgs, extraParams
   sortedCall = (call_ operator,  argsInOrder)

   if ctx.assignTo()
@@ -854,6 +849,21 @@
           callSaturatedKnownCompile ctx, sortedCall
       retrieve call, sortedCall
       compiled
+
+# For use by the IDE, missing arguments are null
+sortedArgs = (paramNames, call) ->
+  args = _labeled _arguments call
+  labeledArgs = labeledToMap args
+  nonLabeledArgs = map _snd, filter (([label, value]) -> not label), args
+  sortCallArguments paramNames, labeledArgs, nonLabeledArgs, []
+
+sortCallArguments = (paramNames, labeledArgs, nonLabeledArgs, extraParams) ->
+  positionalArgs = map id, nonLabeledArgs # copy
+  extraArgs = map id, extraParams
+  (for param in paramNames
+    (lookupInMap labeledArgs, param) or
+      positionalArgs.shift() or
+      extraArgs.shift())

 callConstructorPattern = (ctx, call, extraParamNames) ->
   operator = _operator call
@@ -7341,6 +7351,8 @@

 exports.library = library

+exports.sortedArgs = sortedArgs
+
 exports.isForm = isForm
 exports.isCall = isCall
 exports.isAtom = isAtom


Corrected `param`

Corrected `param`

--- src/zappa.coffee
+++ src/zappa.coffee
@@ -340,7 +340,7 @@
       callback.call ctx, req, res, next, p

   context.param = invariate (k,v) ->
-    @app.param k, build_param v
+    app.param k, build_param v
     return

   # Register a route with express.


support X-As in whoami

support X-As in whoami

--- src/controllers/whoami-controller.coffee
+++ src/controllers/whoami-controller.coffee
@@ -17,7 +17,7 @@
       metadata:
         auth: auth
         fromUuid: auth.uuid
-        toUuid: auth.uuid
+        toUuid: req.get('x-as') ? auth.uuid
         jobType: 'GetDevice'

     jobManager.do 'request', 'response', options, (error, response) =>


collaboration: improve overal stop collaboration

--- client/ide/lib/collaborationcontroller.coffee
+++ client/ide/lib/collaborationcontroller.coffee
@@ -704,7 +704,16 @@
     @setMachineSharingStatus on

-  stopCollaborationSession: (callback = kd.noop) ->
+  # should clean realtime manager.
+  # should delete workspace channel id.
+  # should broadcast the session ended message.
+  # IF USER IS HOST
+  #   should delete private message.
+  #   should set machine sharing status to off.
+  # IF USER IS NOT HOST
+  #   should only call the callback without an error.
+  #   given callback should do the rest. (e.g cleaning-up, quitting..)
+  stopCollaborationSession: (callback) ->

     @chat.settingsPane.endSession.disable()

@@ -719,18 +728,18 @@
       @statusBar.emit 'CollaborationEnded'
       @stopChatSession()
       @modal.destroy()
-      @rtm.dispose()
-      @rtm = null
-      kd.utils.killRepeat @pingInterval
-      kd.singletons.mainView.activitySidebar.emit 'ReloadMessagesRequested'
-      @forEachSubViewInIDEViews_ 'editor', (ep) => ep.removeAllCursorWidgets()
+
+      if @amIHost
+        @setMachineSharingStatus off, (err) =>
+          return callback err  if err
+          @deletePrivateMessage (err) =>
+            return callback err  if err
+            @cleanupCollaboration { reinit: yes }
+      else
+        callback null

     @mySnapshot.clear()
     @rtm.deleteFile @getRealTimeFileName()
-
-    if @amIHost
-      @setMachineSharingStatus off
-      @deletePrivateMessage callback

   getCollaborationHost: -> if @amIHost then nick() else @collaborationHost


DrawingPane: Emit `ChangeHappened` event when drawing board updated.

--- client/ide/workspace/panes/drawingpane.coffee
+++ client/ide/workspace/panes/drawingpane.coffee
@@ -98,6 +98,16 @@
     @$canvas.setLineSize  3
     @$canvas.setLineColor colors.first

+    @$canvas.on 'mouseup mouseleave touchend ', =>
+      @emit 'ChangeHappened', {
+        origin     : KD.nick()
+        type       : 'DrawingBoardUpdated'
+        context    :
+          paneHash : @hash
+          paneType : 'drawing'
+          data     : @getCanvasData()
+      }
+

   addLayerForMenu: ->
     KD.getSingleton('windowController').addLayer @toolbarMenu


#964 - Questions inside a group can't be added to the library

--- jsapp/xlform_model_view/view.row.coffee
+++ jsapp/xlform_model_view/view.row.coffee
@@ -23,7 +23,7 @@
     events:
      # "click .js-expand-row-selector": "expandRowSelector"
      "drop": "drop"
-     "click .js-add-to-question-library": "add_row_to_question_library"
+     #"click .js-add-to-question-library": "add_row_to_question_library"

     initialize: (opts)->
       @options = opts
@@ -57,7 +57,7 @@
       @$el.data("row-index", @model._parent.indexOf @model)
       # @$el.data("row-parent", @model.parentRow().cid)
       @already_rendered = true
-
+
       @
     _renderError: ->
       @$el.addClass("xlf-row-view-error")
@@ -66,7 +66,7 @@
       @
     _renderRow: ->
       @$el.html $viewTemplates.$$render('row.xlfRowView')
-
+      @$('.js-add-to-question-library').click @add_row_to_question_library
       @$label = @$('.card__header-title')
       @$card = @$el.find('.card')
       if 'getList' of @model and (cl = @model.getList())
@@ -86,7 +86,7 @@
         view.renderInRowView(@)

       @
-    add_row_to_question_library: (evt) ->
+    add_row_to_question_library: (evt) =>
       evt.stopPropagation()
       @ngScope.add_row_to_question_library @model


disable spec to test travis failure

disable spec to test travis failure

--- tutor/specs/components/qa.spec.cjsx
+++ tutor/specs/components/qa.spec.cjsx
@@ -77,7 +77,7 @@
       expect(tag.text()).to.not.include('uknown-fake-uuid')
     undefined

-  it 'displays question formats', ->
+  xit 'displays question formats', ->
     wrapper = mount(<Exercises {...@props} />)
     expect(wrapper.find('.formats-listing')).to.have.length(EXERCISES.items.length)
     undefined


do NOT use db-standby for projects table.

--- src/smc-util/db-schema.coffee
+++ src/smc-util/db-schema.coffee
@@ -722,8 +722,10 @@

 schema.projects =
     primary_key: 'project_id'
-    ## a lot depends on this being right at all times, e.g., restart state, so do not use 'unsafe'
-    db_standby : 'safer'
+    ## A lot depends on this being right at all times, e.g., restart state,
+    ## so do not use db_standby yet.
+    ## It is simply not robust enough.
+    ## db_standby : 'safer'
     fields :
         project_id  :
             type : 'uuid',


Improved test case name.

Improved test case name.

--- test/bundle_test.coffee
+++ test/bundle_test.coffee
@@ -3,7 +3,7 @@
 path = require 'path'

 module.exports = class BundleTest extends Janitor.TestCase
-  'test compile': ->
+  'test finding files': ->
     dir = path.join __dirname, 'fixtures', 'sample-package'
     bundle = new Bundle dir
     @assertEqual 1, bundle.files().length


Add option for default hint panel visibility

--- lib/autocomplete-haskell.coffee
+++ lib/autocomplete-haskell.coffee
@@ -20,6 +20,10 @@
       default: 'true'
       description: 'If enabled, hole completions will be shown on \'_\' keystroke.
                     Otherwise, only when there is a prefix, e.g. \'_something\''
+    defaultHintPanelVisibility:
+      type: 'string'
+      default: 'Visible'
+      enum: ['Visible', 'Hidden']

   backend: null
   disposables: null
@@ -42,7 +46,8 @@

       @panel = atom.workspace.addBottomPanel
         item: @view = new LastSuggestionView
-        visible: state.panelVisible
+        visible:
+          state.panelVisible ? (atom.config.get('autocomplete-haskell.defaultHintPanelVisibility') is 'Visible')
         priority: 200

     @globalDisposables.add atom.commands.add 'atom-workspace',


Focus the current editor properly when search is run

--- lib/buffer-find-and-replace-view.coffee
+++ lib/buffer-find-and-replace-view.coffee
@@ -93,7 +93,7 @@

   onActiveItemChanged: =>
     return unless window.rootView
-    editor = rootView.getActiveView()
+    editor = @currentEditor()
     @trigger('active-editor-changed', editor: editor)

   onCursorMoved: =>
@@ -104,7 +104,7 @@
       # crappy boolean. Open to suggestions.
       @cursorMoveOriginatedHere = false
     else
-      rootView.getActiveView().trigger('find-and-replace:clear-current-result')
+      @currentEditor().trigger('find-and-replace:clear-current-result')

   onSearchModelChanged: (model) =>
     @setOptionButtonState(@regexOptionButton, model.getOption('regex'))
@@ -123,6 +123,8 @@
   confirmFind: =>
     @search()
     @findNext()
+    editor = @currentEditor()
+    editor.focus() if editor and editor.searchResults and editor.searchResults.getCurrentResult().total

   confirmReplace: =>
     @search()
@@ -156,20 +158,20 @@
   replaceNext: =>
     @search()
     replacement = @replaceEditor.getText()
-    rootView.getActiveView().trigger('find-and-replace:replace-next', {replacement})
+    @currentEditor().trigger('find-and-replace:replace-next', {replacement})

   replaceAll: =>
     @search()
     replacement = @replaceEditor.getText()
-    rootView.getActiveView().trigger('find-and-replace:replace-all', {replacement})
+    @currentEditor().trigger('find-and-replace:replace-all', {replacement})

   findPrevious: =>
     @cursorMoveOriginatedHere = true # See HACK above.
-    rootView.getActiveView().trigger('find-and-replace:find-previous')
+    @currentEditor().trigger('find-and-replace:find-previous')

   findNext: =>
     @cursorMoveOriginatedHere = true # See HACK above.
-    rootView.getActiveView().trigger('find-and-replace:find-next')
+    @currentEditor().trigger('find-and-replace:find-next')

   toggleRegexOption: => @toggleOption('regex')

@@ -192,4 +194,7 @@
     @active = false
     view.deactivate() for view in @searchResultsViews

-
+  currentEditor: ->
+    rootView.getActiveView()
+
+


Fixes apostrophe

Fixes apostrophe

--- static/scripts/scripts.coffee
+++ static/scripts/scripts.coffee
@@ -1,7 +1,7 @@
 Website = {}

 Website.apps =
-  # My documentation. I'ts works fine too!
+  # My documentation. It's works fine too!
   work: ->
     console.log 'It\'s works fine!'


fix readFileSync bug

fix readFileSync bug

--- lib/precompile.coffee
+++ lib/precompile.coffee
@@ -13,7 +13,7 @@
     if /\.jade$/.test(file)
       name = path.basename(file, ".jade")
       file_path = path.join(src_dir, file)
-      contents = fs.readFileSync(file_path, encoding: "utf8")
+      contents = fs.readFileSync(file_path, "utf8")

       templates[name] = jade.compile contents,
         debug: false