Improve KaTeX sanitization

Improve KaTeX sanitization

--- lib/sanitize.coffee
+++ lib/sanitize.coffee
@@ -91,7 +91,8 @@
 sanitizeHtml.defaults.allowedAttributes.menclose = ['notation']
 sanitizeHtml.defaults.allowedAttributes.mfrac = ['linethickness']
 sanitizeHtml.defaults.allowedAttributes.mi = ['mathvariant']
-sanitizeHtml.defaults.allowedAttributes.mo = ['fence', 'separator', 'stretchy']
+sanitizeHtml.defaults.allowedAttributes.mn = ['mathvariant']
+sanitizeHtml.defaults.allowedAttributes.mo = ['fence', 'mathvariant', 'separator', 'stretchy']
 sanitizeHtml.defaults.allowedAttributes.mstyle = ['mathcolor']
 sanitizeHtml.defaults.allowedTags.push 'nobr'

@@ -114,6 +115,8 @@
   # highlight.js
   'hljs', 'hljs-addition', 'hljs-attr', 'hljs-attribute', 'hljs-built_in', 'hljs-builtin-name', 'hljs-bullet', 'hljs-comment', 'hljs-deletion', 'hljs-emphasis', 'hljs-keyword', 'hljs-link', 'hljs-literal', 'hljs-meta', 'hljs-name', 'hljs-number', 'hljs-params', 'hljs-quote', 'hljs-regexp', 'hljs-section', 'hljs-selector-class', 'hljs-selector-id', 'hljs-selector-tag', 'hljs-string', 'hljs-strong', 'hljs-symbol', 'hljs-tag', 'hljs-template-variable', 'hljs-title', 'hljs-type', 'hljs-variable'
   # KaTeX:
+  'mbin', 'mclose', 'minner', 'mopen', 'mrel', 'mord', 'mop', 'mpunct', 'mtight'
+  # Above exist but not actually styled so not caught by the following:
   # python -c 'import re; print(sorted(set(x.lstrip(".") for x in re.findall(r"\.[-a-zA-Z_][-\w]*", open("node_modules/katex/dist/katex.css").read()))))'
   'accent', 'accent-body', 'accent-full', 'amsrm', 'arraycolsep', 'base', 'boldsymbol', 'boxpad', 'brace-center', 'brace-left', 'brace-right', 'cancel-lap', 'cancel-pad', 'clap', 'col-align-c', 'col-align-l', 'col-align-r', 'delim-size1', 'delim-size4', 'delimcenter', 'delimsizing', 'fbox', 'fcolorbox', 'fix', 'fleqn', 'fontsize-ensurer', 'frac-line', 'halfarrow-left', 'halfarrow-right', 'hdashline', 'hide-tail', 'hline', 'inner', 'katex', 'katex-display', 'katex-html', 'katex-mathml', 'katex-version', 'large-op', 'leqno', 'llap', 'mainrm', 'mathbb', 'mathbf', 'mathboldsf', 'mathcal', 'mathdefault', 'mathfrak', 'mathit', 'mathitsf', 'mathrm', 'mathscr', 'mathsf', 'mathtt', 'mfrac', 'mover', 'mspace', 'msupsub', 'mtable', 'mult', 'munder', 'newline', 'nulldelimiter', 'op-limits', 'op-symbol', 'overlay', 'overline', 'overline-line', 'pstrut', 'reset-size1', 'reset-size10', 'reset-size11', 'reset-size2', 'reset-size3', 'reset-size4', 'reset-size5', 'reset-size6', 'reset-size7', 'reset-size8', 'reset-size9', 'rlap', 'root', 'rule', 'size1', 'size10', 'size11', 'size2', 'size3', 'size4', 'size5', 'size6', 'size7', 'size8', 'size9', 'sizing', 'small-op', 'sout', 'sqrt', 'stretchy', 'strut', 'svg-align', 'tag', 'textbb', 'textbf', 'textboldsf', 'textfrak', 'textit', 'textitsf', 'textrm', 'textscr', 'textsf', 'texttt', 'ttf', 'underline', 'underline-line', 'vertical-separator', 'vlist', 'vlist-r', 'vlist-s', 'vlist-t', 'vlist-t2', 'vs-dashed', 'woff', 'woff2', 'x-arrow', 'x-arrow-pad'
 ]


Tweak Settings to add onLoaded() method.

Settings.onLoaded() is similar to Settings.use(), except it does not
require/expect a key argument.

Would could simulate .onLoaded() with .use() by artificially adding an
(unused) key, but that would make the code less clear.  So it seems
better to have a separate method.

--- lib/settings.coffee
+++ lib/settings.coffee
@@ -23,7 +23,7 @@
       # For UIComponents (or other content of ours in an iframe within a regular page), we can't access
       # localStorage, so we check that the top level frame is also an extension page.
       @cache = if Utils.isBackgroundPage() then localStorage else extend {}, localStorage
-      @onLoaded()
+      @runOnLoadedCallbacks()

     chrome.storage.local.get null, (localItems) =>
       localItems = {} if chrome.runtime.lastError
@@ -34,14 +34,17 @@
         chrome.storage.onChanged.addListener (changes, area) =>
           @propagateChangesFromChromeStorage changes if area == "sync"

-        @onLoaded()
+        @runOnLoadedCallbacks()

   # Called after @cache has been initialized.  On extension pages, this will be called twice, but that does
   # not matter because it's idempotent.
-  onLoaded: ->
-    @log "onLoaded: #{@onLoadedCallbacks.length} callback(s)"
+  runOnLoadedCallbacks: ->
+    @log "runOnLoadedCallbacks: #{@onLoadedCallbacks.length} callback(s)"
     @isLoaded = true
-    callback() while callback = @onLoadedCallbacks.pop()
+    @onLoadedCallbacks.pop()() while 0 < @onLoadedCallbacks.length
+
+  onLoaded: (callback) ->
+    if @isLoaded then callback() else @onLoadedCallbacks.push callback

   shouldSyncKey: (key) ->
     (key of @defaults) and key not in [ "settingsVersion", "previousVersion" ]
@@ -84,8 +87,7 @@

   use: (key, callback) ->
     @log "use: #{key} (isLoaded=#{@isLoaded})"
-    invokeCallback = => callback @get key
-    if @isLoaded then invokeCallback() else @onLoadedCallbacks.push invokeCallback
+    @onLoaded => callback @get key

   # For settings which require action when their value changes, add hooks to this object.
   postUpdateHooks: {}


Bug fix: chibug

Bug fix: chibug

--- lib/simulate.coffee
+++ lib/simulate.coffee
@@ -69,9 +69,9 @@
       dmg = []
       dmg.push damage
       critical = []
-      if support.api_support_airatack.api_stage3.api_ecl_flag[i] == 2
+      if support.api_support_airatack.api_stage3.api_ecl_flag[i] == 1
         critical.push HitType.Critical
-      else if support.api_support_airatack.api_stage3.api_ecl_flag[i] == 1
+      else if support.api_support_airatack.api_stage3.api_ecl_flag[i] == 0 && damage != 0
         critical.push HitType.Hit
       else
         critical.push HitType.Miss


Bug Fix: Missing main gun ci

Bug Fix: Missing main gun ci

--- lib/simulate.coffee
+++ lib/simulate.coffee
@@ -10,6 +10,7 @@
   3: AttackType.Primary_Secondary_CI
   4: AttackType.Primary_Radar_CI
   5: AttackType.Primary_AP_CI
+  6: AttackType.Primary_Primary_CI
 # Map from api id of night attack type to day attack type id
 # 夜戦攻撃種別 0=通常攻撃, 1=連撃, 2=カットイン(主砲/魚雷), 3=カットイン(魚雷/魚雷), 4=カットイン(主砲/副砲), 5=カットイン(主砲/主砲)
 NightAttackTypeMap =


:lipstick: Make event handlers multiine consistent

--- lib/snippets.coffee
+++ lib/snippets.coffee
@@ -101,8 +101,10 @@

   enableSnippetsInEditor: (editorView) ->
     editor = editorView.getEditor()
+
     editorView.command 'snippets:expand', (event) =>
-      event.abortKeyBinding() unless @expandSnippetUnderCursor(editor)
+      unless @expandSnippetUnderCursor(editor)
+        event.abortKeyBinding()

     editorView.command 'snippets:next-tab-stop', (event) ->
       unless editor.snippetExpansion?.goToNextTabStop()


Changed some names. Holmes/Lake always show now

--- lib/stations.coffee
+++ lib/stations.coffee
@@ -5,8 +5,11 @@
   if res.statusCode isnt 200
     return console.log "Failed to get realtime train information",res
   if not _.isArray res.data
-    return console.log "Failed to get meaningful realtime train information",res
+    return console.log "Failed to get meaningful realtime train information",res
   stations = _.uniq(_.pluck(res.data,'STATION')).sort()
+  stations.push("HIGHTOWER STATION")
+  stations.push("WESTLAKE STATION")
+  stations.sort()
   _.each stations,(i)->
    Stations.update {
       name: i
@@ -18,6 +21,9 @@
   Meteor.startup ->
     refreshFromMARTA()
     stationInterval = Meteor.setInterval refreshFromMARTA,60000
+
+geoSort = ->
+

 stationNameMap =
 	"AIRPORT STATION": "Airport"
@@ -36,9 +42,10 @@
 	"EAST LAKE STATION": "East Lake"
 	"EAST POINT STATION": "East Point"
 	"EDGEWOOD CANDLER PARK STATION": "Edgewood/Candler Park"
-	"FIVE POINTS STATION": "Five Point"
+	"FIVE POINTS STATION": "Five Points"
 	"GARNETT STATION": "Garnett"
 	"GEORGIA STATE STATION": "Georgia State University"
+	"HIGHTOWER STATION": "Hamilton Holmes"
 	"INDIAN CREEK STATION": "Indian Creek"
 	"INMAN PARK STATION": "Inman Park"
 	"KENSINGTON STATION": "Kensington"
@@ -51,8 +58,9 @@
 	"NORTH AVE STATION": "North Avenue"
 	"NORTH SPRINGS STATION": "North Springs"
 	"OAKLAND CITY STATION": "Oakland City"
-	"OMNI DOME STATION": "Omni Dome"
+	"OMNI DOME STATION": "Dome/GWCC/Phillips/CNN"
 	"PEACHTREE CENTER STATION": "Peachtree Center"
 	"SANDY SPRINGS STATION": "Sandy Springs"
 	"VINE CITY STATION": "Vine City"
 	"WEST END STATION": "West End"
+	"WESTLAKE STATION": "West Lake"


Fix linting error

Fix linting error

--- lib/tab-view.coffee
+++ lib/tab-view.coffee
@@ -192,7 +192,7 @@

     if @iconName = @item.getIconName?()
       @itemTitle.classList.add('icon', "icon-#{@iconName}")
-    else if @path? and @iconName = FileIcons.getService().iconClassForPath(@path, @)
+    else if @path? and @iconName = FileIcons.getService().iconClassForPath(@path, this)
       unless Array.isArray names = @iconName
         names = names.toString().split /\s+/g


Remove console.log.

Remove console.log.

--- lib/twitter_.coffee
+++ lib/twitter_.coffee
@@ -72,7 +72,6 @@
                                   contentType,
                                   _
         catch err
-            console.log err
             error = new Error "HTTP Error #{err.statusCode}: #{status[err.statusCode]}"
             error.statusCode = err.statusCode
             error.data = err.data


Wrapped up validate

Wrapped up validate

--- lib/validate.coffee
+++ lib/validate.coffee
@@ -10,13 +10,26 @@
       if type.indexOf(':') >= 0
         [type, format] = type.split(':')[0]

-        if type in ['number', 'string', 'boolean']
+      switch type
+        when 'number', 'string', 'boolean'
           return yes if type is typeof value

-        else if type is 'array'
+        when 'array'
           if format?
-          for item in value
-            return no if not @isType(format, item)
-          return yes
+            for item in value
+              return no if not @isType(format, item)
+            return yes
+
+        when 'date'
+          date = new Date(value)
+
+          return no if date.toString is 'Invalid Date'
+          return yes if not format?
+
+          return no if value instanceof Date
+
+          switch format
+            when 'ISO 8061'
+              if date.toISOString() is value then yes else no

   return @


separate checking for updates from checking if installed

--- lib/wakatime.coffee
+++ lib/wakatime.coffee
@@ -37,24 +37,14 @@
     setupConfigs()
     @settingChangedObserver = atom.config.observe 'wakatime', settingChangedHandler

-    lastInit = atom.config.get 'wakatime-hidden.lastInit'
-    currentTime = Math.round (new Date).getTime() / 1000
-
-    hours = 24
-    if not lastInit? or parseInt(lastInit, 10) + 3600 * hours < currentTime or atom.config.get('wakatime.debug')
-      atom.config.set 'wakatime-hidden.lastInit', currentTime
-
-      isPythonInstalled((installed) ->
-        if not installed
-          if os.type() is 'Windows_NT'
-            installPython(checkCLI)
-          else
-            window.alert('Please install Python (https://www.python.org/downloads/) and restart Atom to enable the WakaTime plugin.')
+    isPythonInstalled((installed) ->
+      if not installed
+        if os.type() is 'Windows_NT'
+          installPython(checkCLI)
         else
-          checkCLI()
-      )
-    else
-      finishActivation()
+          window.alert('Please install Python (https://www.python.org/downloads/) and restart Atom to enable the WakaTime plugin.')
+      else
+        checkCLI()

   consumeStatusBar: (statusBar) ->
     statusBarTileView = new StatusBarTileView()
@@ -74,19 +64,31 @@
 checkCLI = () ->
   if not isCLIInstalled()
     installCLI(->
-      log.debug 'Finished installing wakatime cli.'
+      log.debug 'Finished installing wakatime-cli.'
       finishActivation()
     )
   else
-    isCLILatest((latest) ->
-      if not latest
-        installCLI(->
-          log.debug 'Finished installing wakatime cli.'
+
+    # only check for updates to wakatime-cli every 24 hours
+    hours = 24
+
+    lastInit = atom.config.get 'wakatime-hidden.lastInit'
+    currentTime = Math.round (new Date).getTime() / 1000
+    beenawhile = parseInt(lastInit, 10) + 3600 * hours < currentTime
+
+    if not lastInit? or beenawhile or atom.config.get('wakatime.debug')
+      atom.config.set 'wakatime-hidden.lastInit', currentTime
+      isCLILatest((latest) ->
+        if not latest
+          installCLI(->
+            log.debug 'Finished installing wakatime-cli.'
+            finishActivation()
+          )
+        else
           finishActivation()
-        )
-      else
-        finishActivation()
-    )
+      )
+    else
+      finishActivation()

 finishActivation = () ->
   pluginReady = true
@@ -321,7 +323,7 @@
   return dir

 installCLI = (callback) ->
-  log.debug 'Downloading wakatime cli...'
+  log.debug 'Downloading wakatime-cli...'
   statusBarTileView?.setStatus('downloading wakatime-cli...')
   url = 'https://github.com/wakatime/wakatime/archive/master.zip'
   zipFile = __dirname + path.sep + 'wakatime-master.zip'


Removed redundant code, as we already unset the event handler

--- js-library/app/js/loader.coffee
+++ js-library/app/js/loader.coffee
@@ -40,13 +40,10 @@
 else
   coreScriptTag.src = FactlinkConfig.lib + FactlinkConfig.srcPath

-scriptLoaded = false
 coreScriptTag.onload = coreScriptTag.onreadystatechange = ->
-  return false if scriptLoaded
   return false if coreScriptTag.readyState? not in ['complete', 'loaded']

   coreScriptTag.onload = coreScriptTag.onreadystatechange = null
-  scriptLoaded = true

   window.FACTLINK = iframe.contentWindow.Factlink
   jQuery?(window).trigger 'factlink.libraryLoaded'


Fix teacher edition content detection

Fix teacher edition content detection

--- src/scripts/modules/media/tabs/tools/tools.coffee
+++ src/scripts/modules/media/tabs/tools/tools.coffee
@@ -17,7 +17,7 @@
       derivable: @model.canEdit()
       isEditable: @isEditable
       # FIX: Detect if an element actually has the class `os-teacher`
-      hasTeachersEdition: () => @model.get('content')?.indexOf('os-teacher') >= 0
+      hasTeachersEdition: () => @model.asPage()?.get('content')?.indexOf('os-teacher') >= 0
       isTeacher: @model.get('teacher')
     }


clean up decryption

clean up decryption

--- js/src/KryptnosticObject.coffee
+++ js/src/KryptnosticObject.coffee
@@ -31,8 +31,7 @@
         chunkingStrategy      = new chunkingStrategyClass()
         decryptedBlocks       = @body.data.map((chunk) -> cryptoService.decrypt(chunk.block))
         data                  = chunkingStrategy.join(decryptedBlocks)
-        body                  = _.extend({}, this.body, {data})
-        raw                   = _.extend({}, _.cloneDeep(this), {body})
+        raw                   = _.extend({}, _.cloneDeep(this), {body: {data}})
         return new KryptnosticObject(raw)
       else
         return this


start using cache for directoryApi calls

--- js/src/http/DirectoryApi.coffee
+++ js/src/http/DirectoryApi.coffee
@@ -8,6 +8,7 @@
   'kryptnostic.requests'
   'kryptnostic.block-ciphertext'
   'kryptnostic.validators'
+  'kryptnostic.caching-service'
 ], (require) ->

   axios             = require 'axios'
@@ -18,6 +19,7 @@
   BlockCiphertext   = require 'kryptnostic.block-ciphertext'
   Promise           = require 'bluebird'
   validators        = require 'kryptnostic.validators'
+  Cache             = require 'kryptnostic.caching-service'

   { validateId }    = validators

@@ -44,6 +46,9 @@

     # returns a serialized cryptoservice for the requested object
     getObjectCryptoService: (objectId) ->
+      cached = Cache.get( Cache.CRYPTO_SERVICES, objectId )
+      if cached?
+        return cached
       Promise.resolve()
       .then ->
         validateId(objectId)
@@ -54,6 +59,8 @@
         })))
       .then (response) ->
         serializedCryptoService = response.data.data
+        if serializedCryptoService
+          Cache.store( Cache.CRYPTO_SERVICES, objectId, serializedCryptoService )
         log.info('getObjectCryptoService', { objectId })
         return serializedCryptoService

@@ -118,6 +125,9 @@

     # gets the public key of a user in the same realm as the caller.
     getPublicKey: (uuid) ->
+      cached = Cache.get(Cache.PUBLIC_KEYS, uuid )
+      if cached?
+        return cached
       Promise.resolve(axios(Requests.wrapCredentials({
         url    : publicKeyUrl() + '/' + uuid
         method : 'GET'
@@ -125,13 +135,18 @@
       .then (response) ->
         envelope = response.data
         log.debug('getPublicKey', { envelope })
-        return new PublicKeyEnvelope(envelope)
+        keyEnv = new PublicKeyEnvelope(envelope)
+        Cache.store( Cache.PUBLIC_KEYS, uuid, keyEnv )
+        return keyEnv
       .catch (e) ->
         return undefined

     # gets the user's encrypted salt.
     # request is not wrapped because the user has not auth'ed yet.
     getSalt: (uuid) ->
+      cached = Cache.get( Cache.SALTS, uuid )
+      if cached?
+        return cached
       Promise.resolve(axios({
         url    : saltUrl() + '/' + uuid
         method : 'GET'
@@ -142,7 +157,9 @@
         if _.isEmpty(ciphertext)
           throw new Error 'incorrect credentials'
         else
-          return new BlockCiphertext(ciphertext)
+          ciphertext = new BlockCiphertext(ciphertext)
+          Cache.store( Cache.SALTS, uuid, ciphertext )
+          return ciphertext

     # sets the encrypted salt for a new user account.
     # manually sets principal and credential headers since user has not auth'ed yet.


fix(api): Don't display auth failure bar for req with no auth (onboarding)

--- src/flux/nylas-api.coffee
+++ src/flux/nylas-api.coffee
@@ -231,6 +231,9 @@
       return Promise.resolve()

   _handleAuthenticationFailure: (modelUrl, apiToken) ->
+    # prevent /auth errors from presenting auth failure notices
+    return Promise.resolve() unless apiToken
+
     AccountStore ?= require './stores/account-store'
     account = AccountStore.accounts().find (account) ->
       AccountStore.tokenForAccountId(account.id) is apiToken


:memo: Update documentation for ::getConfigValue

Adds the `key` argument to the function documentation

--- src/git-repository.coffee
+++ src/git-repository.coffee
@@ -238,6 +238,7 @@

   # Public: Returns the git configuration value specified by the key.
   #
+  # * `key`  The {String} key for the configuration to lookup.
   # * `path` An optional {String} path in the repository to get this information
   #   for, only needed if the repository has submodules.
   getConfigValue: (key, path) -> @getRepo(path).getConfigValue(key)


do correct calls since the scope changes

--- client/app/Applications/Activity.kdapplication/views/discussionactivityactions.coffee
+++ client/app/Applications/Activity.kdapplication/views/discussionactivityactions.coffee
@@ -38,8 +38,8 @@

     for view in [@opinionCount, @commentCount]
       view.on "countChanged", (count)->
-        if count > 0 then view.show()
-        else view.hide()
+        if count > 0 then @show()
+        else @hide()

     @on "DiscussionActivityLinkClicked", =>
       unless @parent instanceof ContentDisplayDiscussion


#1040 - Skip logic always uses first option

--- jsapp/xlform_model_view/view.rowDetail.SkipLogic.coffee
+++ jsapp/xlform_model_view/view.rowDetail.SkipLogic.coffee
@@ -253,7 +253,7 @@
         @presenter.change_operator @operator_picker_view.value

     bind_response_value: () ->
-      @$response_value.on (if @$response_value.prop('tagName') == 'select' then 'change' else 'blur'), () =>
+      @$response_value.on (if @$response_value.prop('tagName').toLowerCase() == 'select' then 'change' else 'blur'), () =>
         @presenter.change_response @$response_value.val()

     response_value_handler: () ->


DevTools: Only compile coffee files and run plain js files, ignore others

--- client/DevTools/AppView.coffee
+++ client/DevTools/AppView.coffee
@@ -114,9 +114,14 @@
     return  if @_inprogress
     @_inprogress = yes

-    time 'Compile took:'
-
     {JSEditor, PreviewPane} = @workspace.activePanel.panesByName
+    extension = JSEditor.getData()?.getExtension() or 'coffee'
+
+    if extension not in ['js', 'coffee']
+      @_inprogress = no
+      log "Not supported!"
+      PreviewPane.container.destroySubViews()
+      return

     @compiler (coffee)=>

@@ -127,8 +132,9 @@

       try

-        coffee.compile code
-        coffee.run code
+        switch extension
+          when 'js' then eval code
+          when 'coffee' then coffee.run code

         PreviewPane.container.addSubView window.appView

@@ -144,7 +150,6 @@
         delete window.appView
         @_inprogress = no

-        timeEnd 'Compile took:'

   previewCss:(force = no)->


Enable push in client via settings

Enable push in client via settings

--- client/lib/cordova/push.coffee
+++ client/lib/cordova/push.coffee
@@ -1,28 +1,29 @@
 if Meteor.isCordova
-	Push.Configure {}
+	if RocketChat.settings.get('Push_enable') is true
+		Push.Configure {}

-	Push.addListener 'token', (token) ->
-		Meteor.call 'log', 'token', arguments
+		Push.addListener 'token', (token) ->
+			Meteor.call 'log', 'token', arguments

-	Push.addListener 'error', (err) ->
-		Meteor.call 'log', 'error', arguments
-		if error.type == 'apn.cordova'
-			Meteor.call 'log', err.error
+		Push.addListener 'error', (err) ->
+			Meteor.call 'log', 'error', arguments
+			if error.type == 'apn.cordova'
+				Meteor.call 'log', err.error

-	Push.addListener 'register', (evt) ->
-		Meteor.call 'log', 'register', arguments
+		Push.addListener 'register', (evt) ->
+			Meteor.call 'log', 'register', arguments

-	Push.addListener 'alert', (notification) ->
-		Meteor.call 'log', 'alert', arguments
+		Push.addListener 'alert', (notification) ->
+			Meteor.call 'log', 'alert', arguments

-	Push.addListener 'sound', (notification) ->
-		Meteor.call 'log', 'sound', arguments
+		Push.addListener 'sound', (notification) ->
+			Meteor.call 'log', 'sound', arguments

-	Push.addListener 'badge', (notification) ->
-		Meteor.call 'log', 'badge', arguments
+		Push.addListener 'badge', (notification) ->
+			Meteor.call 'log', 'badge', arguments

-	Push.addListener 'startup', (notification) ->
-		Meteor.call 'log', 'startup', arguments
+		Push.addListener 'startup', (notification) ->
+			Meteor.call 'log', 'startup', arguments

-	Push.addListener 'message', (notification) ->
-		Meteor.call 'log', 'message', arguments+		Push.addListener 'message', (notification) ->
+			Meteor.call 'log', 'message', arguments


bugfix: parens necessary here.

bugfix: parens necessary here.

--- coffee/src/shells/shell.coffee
+++ coffee/src/shells/shell.coffee
@@ -179,7 +179,7 @@
     @$el.height @height()

     # if ready when call stack clears, announce readiness
-    setTimeout (=> @trigger 'MediaView:Ready' if @ready), 0
+    setTimeout (=> @trigger('MediaView:Ready') if @ready), 0

     @


fix(table): Using columnsOrder when creating new rows instead of key order of columnsMap

--- src/services/table.coffee
+++ src/services/table.coffee
@@ -126,8 +126,9 @@

         make: (section, model) ->
           row = tableComponents.rowFactory(section, model)
-          for colName, column of @table.columnsMap
-            cell = tableComponents.cellFactory(row, column)
+          for colName in @table.columnsOrder
+            column                = @table.columnsMap[colName]
+            cell                  = tableComponents.cellFactory(row, column)
             row.cellsMap[colName] = cell
             row.cells.push cell
           row


Add URI::mergeParams(object) method

Add URI::mergeParams(object) method

--- src/marbles/http/uri.js.coffee
+++ src/marbles/http/uri.js.coffee
@@ -35,3 +35,13 @@
       uri = uri_or_string
     (uri.scheme == @scheme) and (uri.hostname == @hostname) and (uri.port == @port) and (uri.path == @path)

+  mergeParams: (params) =>
+    params = for k,v of params
+      v = if typeof v.join is 'function'
+        encodeURIComponent(v.join(','))
+      else
+        encodeURIComponent(v)
+      "#{encodeURIComponent(k)}=#{v}"
+    separator = if @path.match(/\?/) then "&" else "?"
+    @path += "#{separator}#{params.join('&')}" if params.length
+


Bump to v0.1.1

Bump to v0.1.1

--- jquery.infinite-pages.js.coffee
+++ jquery.infinite-pages.js.coffee
@@ -1,5 +1,5 @@
 ###
-jQuery Infinite Pages v0.1.0
+jQuery Infinite Pages v0.1.1
 https://github.com/magoosh/jquery-infinite-pages

 Released under the MIT License


Users: make it so the Users component also displays non-collaborator names properly

--- src/smc-webapp/users.cjsx
+++ src/smc-webapp/users.cjsx
@@ -25,8 +25,24 @@

 {TimeAgo} = require('./r_misc')

+{salvus_client} = require('./salvus_client')   # needed for getting non-collaborator user names
+
 # Register the actions
-redux.createActions('users')
+class UsersActions extends Actions
+    fetch_non_collaborator: (account_id) =>
+        salvus_client.get_usernames
+            account_ids : [account_id]
+            cb          : (err, x) =>
+                if err
+                    console.warn("ERROR getting username for account with id '#{account_id}'")
+                else
+                    obj = x[account_id]
+                    if obj?
+                        obj.account_id = account_id
+                        @setState
+                            user_map : {"#{account_id}": obj}
+
+actions = redux.createActions('users', UsersActions)

 # Define user store: all the users you collaborate with
 class UsersStore extends Store
@@ -43,6 +59,15 @@
         m = @getIn(['user_map', account_id])
         if m?
             return "#{m.get('first_name')} #{m.get('last_name')}"
+        else
+            m = @getIn(['other_names', account_id])
+            if m?
+                return m
+            # look it up, which causes it to get saved in the store, which causes a new render later.
+            actions.fetch_non_collaborator(account_id)
+            # for now will just return undefined; when store gets updated with other_names
+            # knowing the account_id, then component will re-reender.
+            return

     get_last_active: (account_id) =>
         return @getIn(['user_map', account_id, 'last_active'])
@@ -61,7 +86,7 @@
             if c then c else misc.cmp(@get_last_name(a.account_id), @get_last_name(b.account_id))

 # Register user store
-redux.createStore('users', UsersStore)
+store = redux.createStore('users', UsersStore)

 # Create and register projects table, which gets automatically
 # synchronized with the server.
@@ -103,10 +128,13 @@
         return misc.trunc_middle((@props.name ? "#{info.first_name} #{info.last_name}"), 50)

     render : ->
+        if not @props.user_map? or @props.user_map.size == 0
+            return Loading...
         info = @props.user_map?.get(@props.account_id)
         if not info?
+            actions.fetch_non_collaborator(@props.account_id)
             return Loading...
         else
             info = info.toJS()
-            {@name(info)}{@render_last_active()}
+            return {@name(info)}{@render_last_active()}


Fix taps, start force tap

Fix taps, start force tap

--- framer/GestureInputRecognizer.coffee
+++ framer/GestureInputRecognizer.coffee
@@ -22,8 +22,10 @@
 		@session = null

 	touchstart: (event) =>
+
 		# Only fire if we are not already in a session
 		return if @session
+
 		@em.wrap(window).addEventListener("touchmove", @touchmove)
 		@em.wrap(window).addEventListener("touchend", @touchend)

@@ -66,10 +68,10 @@

 	# Tap

-	tap: => @_dispatchEvent("tap", event)
-	tapstart: => @_dispatchEvent("tapstart", event)
-	tapend: => @_dispatchEvent("tapend", event)
-	doubletap: => @_dispatchEvent("doubletap", event)
+	tap: (event) => @_dispatchEvent("tap", event)
+	tapstart: (event) => @_dispatchEvent("tapstart", event)
+	tapend: (event) => @_dispatchEvent("tapend", event)
+	doubletap: (event) => @_dispatchEvent("doubletap", event)

 	# Press

@@ -83,6 +85,19 @@

 	longpressend: (event) =>
 		@_dispatchEvent("longpressend", event)
+
+	# ForceTap
+
+	forcetapstart: (event) =>
+		print "forcetapstart"
+		return unless @session
+		return if @session.started.forcetap
+		@session.started.forcetap = event
+		@_dispatchEvent("forcetapstart", event)
+		@_dispatchEvent("forcetap", event)
+
+	forcetapend: =>
+		@_dispatchEvent("forcetapend", event)

 	# Pan


Fixes issue with any milestone value in dropdown not being selectable

Closes #14293

--- app/assets/javascripts/milestone_select.js.coffee
+++ app/assets/javascripts/milestone_select.js.coffee
@@ -46,7 +46,7 @@
           milestone.title
         id: (milestone) ->
           if !useId
-            if milestone.title isnt "Any milestone"
+            if milestone.title isnt "Any Milestone"
               milestone.title
             else
               ""


cleared installations code

cleared installations code

--- app/assets/javascripts/pages/installations.coffee
+++ app/assets/javascripts/pages/installations.coffee
@@ -1,7 +1,5 @@
 PubSub.subscribe('assigned.website', (ev, website)->
-  $.getJSON Routes.api_v1_website_path(website.id), (data) ->
-    $('#current-website').render data
-    apiKeyTab(data)
+  apiKeyTab(data)

   $.getJSON Routes.api_v1_notification_path(gon.notification_id), { member_id: $.auth.user.id }, (data) ->
     $('input[name=daily_reports]').attr('checked', true) if data.daily_reports
@@ -45,44 +43,48 @@
         swal('Key updated')
     return

+switchIndexTabs = () ->
+  $('#configuration-tabs').on 'click', (e) ->
+    target = $(e.target.closest("li"))
+    e.preventDefault()
+    $('#configuration-tabs li').removeClass('active')
+
+    if target.attr('name') == 'details'
+      $('#details-settings, #client-details').show()
+      $('#client-configuration').hide()
+    else if target.attr('name') == 'integrations'
+      $('#client-details, #client-configuration').hide()
+    else if target.attr('name') == 'client configuration'
+      $('#client-configuration, #client-information, #client-platforms, #client-frameworks').show()
+      $('#client-details').hide()
+    target.addClass('active')
+
+  $('#platforms-tabs, #details-tabs').on 'click', (e) ->
+    target = $(e.target.closest("li"))
+    $('#platforms-tabs li').removeClass('active')
+    $('#details-tabs li').removeClass('active')
+    target.addClass('active')
+    manipulateInstallationsIndex(target)
+
+  $('#img-platforms').on 'click', (e) ->
+    manipulateInstallationsIndex($(e.target).parent())
+    $('#platforms-tabs li').removeClass('active')
+
 PubSub.subscribe('assigned.website', (ev, website)->
   switch gon.action
     when "index"
-      hideIndexHtml()
+      hideListedTabs()
+      switchIndexTabs()
       $('#client-details').hide()
+      $('#integrations-details').hide()
       $('#client-information, #client-platforms, #client-frameworks').show()
+
       $.getJSON Routes.api_v1_website_path(website.id), (data) ->
         $('#current-website').render data

-      $('#configuration-tabs').on 'click', (e) ->
-        target = $(e.target.closest("li"))
-        e.preventDefault()
-        $('#configuration-tabs li').removeClass('active')
-
-        if target.attr('name') == 'details'
-          $('#client-details').show()
-          $('#details-settings').show()
-          $('#client-configuration').hide()
-        else if target.attr('name') == 'integrations'
-          $('#client-details').hide()
-          $('#client-configuration').hide()
-        else if target.attr('name') == 'client configuration'
-          $('#client-configuration').show()
-          $('#client-details').hide()
-        target.addClass('active')
-
-      $('#platforms-tabs, #details-tabs').on 'click', (e) ->
-        target = $(e.target.closest("li"))
-        $('#platforms-tabs, #details-tabs li').removeClass('active')
-        target.addClass('active')
-        manipulateInstallationsIndex(target)
-
-      $('#img-platforms').on 'click', (e) ->
-        manipulateInstallationsIndex($(e.target).parent())
-        $('#platforms-tabs li').removeClass('active')

-hideIndexHtml = () ->
+hideListedTabs = () ->
   $('#details-settings,
     #details-notifications,
     #current-site, #details-rate-limits,
@@ -100,7 +102,7 @@
   ).hide()

 manipulateInstallationsIndex = (target) ->
-  hideIndexHtml()
+  hideListedTabs()
   if target.attr('name') == 'javascript'
     $('#current-site').show()
     $('#javascript').show()


add a class when upload fails to the upload template

--- app/assets/javascripts/s3_direct_upload.js.coffee
+++ app/assets/javascripts/s3_direct_upload.js.coffee
@@ -20,7 +20,8 @@
     remove_completed_progress_bar: true
     remove_completed_uploads: true
     remove_failed_progress_bar: false
-    remove_failed_upload_template: false
+    remove_failed_uploads: false
+    failed_upload_class: "failed"
     progress_bar_target: null
     filename_target: null
     click_submit_target: null
@@ -126,7 +127,13 @@
         content = build_content_object $uploadForm, data.files[0], data.result
         content.error_thrown = data.errorThrown

-        data.context.remove() if data.context && settings.remove_failed_progress_bar # remove progress bar
+        if settings.allow_multiple_files
+          data.context.addClass(settings.failed_upload_class)
+          data.context.find(settings.progress_bar_target).remove() if data.context.find(settings.progress_bar_target) && settings.remove_failed_progress_bar # remove progress bar
+          data.context.remove() if data.context && settings.remove_failed_uploads # remove uploads
+        else
+          data.context.remove() if data.context && settings.remove_failed_progress_bar # remove progress bar
+
         $uploadForm.trigger("s3_upload_failed", [content])

       formData: (form) ->


webapp/tasks: moving help page

webapp/tasks: moving help page

--- src/smc-webapp/tasks/actions.coffee
+++ src/smc-webapp/tasks/actions.coffee
@@ -4,7 +4,7 @@

 LAST_EDITED_THRESH_S = 30

-WIKI_HELP_URL = "https://github.com/sagemathinc/cocalc/wiki/tasks"
+WIKI_HELP_URL = "https://doc.cocalc.com/tasks.html"

 immutable  = require('immutable')
 underscore = require('underscore')


don't change url for token form

don't change url for token form

--- app/assets/javascripts/states/main/main.js.coffee
+++ app/assets/javascripts/states/main/main.js.coffee
@@ -15,7 +15,7 @@
       return true

   "login_token":
-    url:   "/login/token"
+    url:   "/login"
     views: [ _bv.LoginToken ]

   "forgot":


Move gphotos for tabs all

Move gphotos for tabs all

--- app/assets/javascripts/uploadcare/settings.coffee
+++ app/assets/javascripts/uploadcare/settings.coffee
@@ -52,7 +52,7 @@

   presets =
     tabs:
-      all: 'file camera url facebook gdrive dropbox instagram evernote flickr skydrive box vk huddle gphotos'
+      all: 'file camera url facebook gdrive gphotos dropbox instagram evernote flickr skydrive box vk huddle'
       default: defaults.tabs


Feature [#44907875] Removes debugging statement

On successful preferences save, don't console.log the returned resource.

--- app/assets/javascripts/views/users/edit.js.coffee
+++ app/assets/javascripts/views/users/edit.js.coffee
@@ -46,7 +46,6 @@
             model.membership.set resource
             @model.preferences.save preferencesAttributes,
               success: (resource, response) ->
-                console.log resource
                 Backbone.history.navigate("/users", true)
               error: (model, xhr, options) =>
                 @errorHandler xhr, "preferences"


Forwarding all non-existent calls to `index.html` - the app should display a 404.

--- src/polvo/core/tentacle.coffee
+++ src/polvo/core/tentacle.coffee
@@ -49,9 +49,13 @@
   serve:->
     root = @config.server.root
     port = @config.server.port
+    index = path.join root, 'index.html'

     # simple static server with 'connect'
-    @conn = connect().use(connect.static root).listen port
+    @conn = connect()
+              .use( connect.static root )
+              .use( (req, res)-> res.end (fs.readFileSync index, 'utf-8') )
+              .listen port

     # plugging socket io (only for development mode - excludes -r option)
     unless @cli.argv.r


Fix own messages showing as unread.

Fix own messages showing as unread.

--- src/ui/models/conv.coffee
+++ src/ui/models/conv.coffee
@@ -141,7 +141,7 @@
     return 0 unless typeof t == 'number'
     c = 0
     for e in conv?.event ? []
-        c++ if e.chat_message and e.timestamp > t
+        c++ if e.chat_message and e.timestamp > t and not entity.isSelf e.sender_id.chat_id
         return MAX_UNREAD if c >= MAX_UNREAD
     c


Add cors support for ie9 and below

Add cors support for ie9 and below

--- bucky.coffee
+++ bucky.coffee
@@ -50,14 +50,13 @@
     # times?
     decimalPrecision: 3

-    # Bucky can automatically report a datapoint of what it's response time
+    # Bucky can automatically report a datapoint of what its response time
     # is.  This is useful because Bucky returns almost immediately, making
     # it's response time a good measure of the user's connection latency.
     sendLatency: false

     # Downsampling will cause Bucky to only send data 100*SAMPLE percent
-    # of the time.  It is used to reduce the amount of data sent to the backend.  Keep
-    # in mind that the goal is to have each datapoint sent at least once per minute.
+    # of the time.  It is used to reduce the amount of data sent to the backend.
     #
     # It's not done per datapoint, but per client, so you probably don't want to downsample
     # on node (you would be telling a percentage of your servers to not send data).
@@ -136,6 +135,49 @@
     unless maxTimeout?
       maxTimeout = setTimeout flush, options.maxInterval

+  makeRequest = (data) ->
+    corsSupport = window.XMLHttpRequest and (XMLHttpRequest.defake or 'withCredentials' of new XMLHttpRequest())
+
+    match = /(?:(https?:\/\/)|(?:\/))?([^\/])/.exec options.host
+
+    if match?[1]
+      # FQDN
+
+      origin = match[1] + match[2]
+      if origin is "#{ document.location.protocol }//#{ document.location.host }"
+        sameOrigin = true
+      else
+        sameOrigin = false
+    else
+      # Relative URL
+
+      sameOrigin = true
+
+    sendStart = now()
+
+    body = JSON.stringify data
+
+    if not sameOrigin and not corsSupport and window.XDomainRequest?
+      # CORS support for IE9
+      req = new XDomainRequest
+
+      # Required for XDomainRequest, as application/json is not supported
+      req.setRequestHeader 'Content-Type', 'text/plain'
+    else
+      req = new _XMLHttpRequest
+
+      req.setRequestHeader 'Content-Type', 'application/json'
+
+    req.open 'POST', "#{ options.host }/send", true
+
+    req.addEventListener 'load', ->
+      updateLatency(now() - sendStart)
+    , false
+
+    req.send body
+
+    req
+
   sendQueue = ->
     if not ACTIVE
       log "Would send bucky queue"
@@ -162,21 +204,7 @@
       if point.count isnt 1
         out[key] += "@#{ round(1 / point.count, 5) }"

-    sendStart = now()
-
-    body = JSON.stringify out
-
-    request = new _XMLHttpRequest
-    request.open 'POST', "#{ options.host }/send", true
-
-    request.setRequestHeader 'Content-Type', 'application/json'
-    request.setRequestHeader 'Content-Length', body.length
-
-    request.addEventListener 'load', ->
-      updateLatency(now() - sendStart)
-    , false
-
-    request.send body
+    makeRequest out

     queue = {}


don't clean up the key anymore

don't clean up the key anymore

--- scripts/poo-tracker.coffee
+++ scripts/poo-tracker.coffee
@@ -16,9 +16,6 @@

       room = process.env.HUBOT_IRC_ROOMS or "#arrakis"
       robot.send room, reply
-
-      # clean up
-      redis_client.del POO_TRACKER_KEY
       redis_client.set POO_LATEST_KEY, reply

   robot.respond /poo tracker( me)?/i, (res) ->


Change to finding min instead of sorting.

--- web/volume/slot.coffee
+++ web/volume/slot.coffee
@@ -12,7 +12,7 @@
     $scope.authorized = models.Login.isAuthorized()
     $flow = $scope.$flow # not really in this scope

-    ################################### Base classes/utilities
+    #################################### Base classes/utilities

     # Represents a point on the timeline, which can be expressed as "o" (offset time in ms), "x" (pixel X position in client coordinates), or "p" (fractional position on the timeline, may escape [0,1])
     class TimePoint
@@ -245,7 +245,7 @@
       resetRange()
       return

-    ################################### Video/playback controls
+    #  ---- Video/playback controls

     $scope.updatePosition = () ->
       seg = (if $scope.editing == 'position' then $scope.current else $scope.asset?.segment)
@@ -295,19 +295,24 @@
         return
       ended: ->
         $scope.playing = 0
-        return unless $scope.asset && isFinite(p = $scope.asset.segment.u)
-        # look for something else to play:
-        for i in $scope.assets when i.asset && !i.asset.pending && i.asset.format.type == 'video' && i.asset.checkPermission(constants.permission.VIEW) && i.lBounded && i.ut.o > p
-          a = i
-          break
-        return unless a
-        a.choose(true)
+
+        asset = _.chain($scope.assets)
+                   .filter (i) -> i.lt.o >= $scope.asset.segment.u && i.asset.format.type == 'video'
+                   .min (i) -> i.lt.o
+                   .value()
+        if asset?
+          asset.choose()
+
+          $timeout ->
+            $scope.play()
+            $scope.playing = 1
+          , 1000
+
         return

     for ev, fn of videoEvents
       videoEvents[ev] = $scope.$lift(fn)

-    autoplay = undefined
     @deregisterVideo = (v) ->
       return unless video == v
       video = undefined
@@ -319,12 +324,9 @@
       video = v
       seekOffset(ruler.position.o)
       v.on(videoEvents)
-      if autoplay
-        autoplay = false
-        video[0].play()
-      return
-
-    ################################### Player display
+      return
+
+    #  ---- Player display

     playerMinHeight = 200
     viewportMinHeight = 120
@@ -374,14 +376,14 @@
         setPlayerHeight()
       return

-    ################################### Track management
+    #  ---- Track management

     stayDirty = (global) ->
       if global || editing && $scope.current && $scope.form.edit && ($scope.current.asset || $scope.current.record) && ($scope.current.dirty = $scope.form.edit.$dirty)
         not confirm(constants.message('navigation.confirmation'))

     class TimeBar extends TimeSegment
-      choose: (ap) ->
+      choose: ->
         return false if stayDirty()

         $scope.current = this
@@ -399,7 +401,6 @@
         $scope.playing = 0
         finalizeSelection()
         updatePlayerHeight()
-        autoplay = ap
         true

       click: (event) ->
@@ -448,7 +449,7 @@
     unchoose = TimeBar.prototype.choose.bind(undefined)
     $scope.click = TimeBar.prototype.click.bind(undefined)

-    ################################### Selection (temporal) management
+    #  ---- Selection (temporal) management

     $scope.setSelectionEnd = (u) ->
       pos = ruler.position.o
@@ -511,7 +512,7 @@
       else
         ruler.selection

-    ################################### Track implementations
+    #  ---- Track implementations

     class Asset extends TimeBar
       constructor: (asset) ->
@@ -1125,7 +1126,7 @@
             body: constants.message('comments.add.error')
             report: e

-    ################################### Initialization
+    #  ---- Initialization

     $scope.tags = (new Tag(tag) for tagId, tag of slot.tags when (if editing then tag.keyword?.length else tag.coverage?.length))
     $scope.comments = (new Comment(comment) for comment in slot.comments)


add color to rect draw

add color to rect draw

--- src/render/CanvasRender.coffee
+++ src/render/CanvasRender.coffee
@@ -165,5 +165,7 @@
      # @param {int} width - Width of your rectangle
      # @param {int} height - Height of your rectangle
     ###
-    rect: (x, y, width, height) ->
-        @.getContext().rect x, y, width, height
+    rect: (x, y, width, height, color) ->
+        if color
+            @.getContext().fillStyle = color
+        @.getContext().fillRect x, y, width, height


renderer itemUtils.setPropertyValue added

--- src/renderer/utils/item.coffee
+++ src/renderer/utils/item.coffee
@@ -366,3 +366,15 @@
         utils.defineProperty prototype, name, null, getter, setter

         prototype
+
+    setPropertyValue: (item, prop, val) ->
+        assert.instanceOf item, Renderer.Item
+        assert.isString prop
+
+        internalName = getPropInternalName prop
+        signalName = getPropHandlerName prop
+
+        oldVal = item[internalName]
+        item[internalName] = val
+        emitSignal item, signalName, oldVal
+        return


Use soft wrap instead of font size in example hack

--- dot-atom/init.coffee
+++ dot-atom/init.coffee
@@ -4,10 +4,11 @@
 # after packages are loaded/activated and after the previous editor state
 # has been restored.
 #
-# An example hack to make opened Markdown files have larger text:
+# An example hack to make opened Markdown files always be soft wrapped:
 #
 # path = require 'path'
 #
 # atom.workspaceView.eachEditorView (editorView) ->
-#   if path.extname(editorView.getEditor().getPath()) is '.md'
-#     editorView.setFontSize(24)
+#   editor = editorView.getEditor()
+#   if path.extname(editor.getPath()) is '.md'
+#     editor.setSoftWrap(true)


Client: Landing: Teams: Remove old landing page.

--- client/landing/site.landing/coffee/teams/AppController.coffee
+++ client/landing/site.landing/coffee/teams/AppController.coffee
@@ -3,9 +3,12 @@
 TeamsView         = require './AppView'
 TeamSelectorView  = require './teamselectorview'

+
 module.exports = class TeamsAppController extends kd.ViewController

+
   kd.registerAppClass this, name : 'Teams'
+

   constructor: (options = {}, data) ->


Use same code to print extension-installation message

--- core/app/backbone/views/react_install_extension_button.coffee
+++ core/app/backbone/views/react_install_extension_button.coffee
@@ -5,6 +5,7 @@
 window.ReactInstallExtensionButton = React.createClass
   displayName: 'ReactInstallExtensionButton'
   render: ->
+    browserName = determineBrowser()
     extra_class = if @props.huge_button then 'button-huge' else null
     if document.documentElement.getAttribute('data-factlink-extension-loaded') != undefined
       _div [],
@@ -16,13 +17,13 @@
         _div ['visible-when-chrome'],
           _a ['button-success', extra_class, href: 'javascript:chrome.webstore.install()'],
             _span ['install-chrome']
-            'Install Factlink for Chrome'
+            'Install Factlink for ' + browserName.capitalize()
         _div ['visible-when-firefox'],
           _a ['button-success', extra_class, href: 'https://static.factlink.com/extension/firefox/factlink-latest.xpi'],
             _span ['install-firefox']
-            'Install Factlink for Firefox'
+            'Install Factlink for ' + browserName.capitalize()
         _div ['visible-when-safari'],
           _a ['button-success', extra_class, href: 'https://static.factlink.com/extension/firefox/factlink.safariextz'],
             _span ['install-safari']
-            'Install Factlink for Safari'
+            'Install Factlink for ' + browserName.capitalize()


Don't use project path as fallback icon for item with no path

Stops the project folder icon appearing in places that don't make sense
like settings and new files.

Test Plan:

  * Opened settings and didn't see the project folder icon
  * Closed all tabs and saw the project folder icon

--- src/workspace-view.coffee
+++ src/workspace-view.coffee
@@ -217,7 +217,7 @@
     if projectPath = atom.project.getPath()
       if item = @getModel().getActivePaneItem()
         title = "#{item.getTitle?() ? 'untitled'} - #{projectPath}"
-        proxyIconPath = item.getPath?() ? projectPath
+        proxyIconPath = item.getPath?() ? ''
         @setTitle(title, proxyIconPath)
       else
         @setTitle(projectPath, projectPath)


Separate into two files

Separate into two files

--- vendor/assets/javascripts/fustrate/_module.coffee
+++ vendor/assets/javascripts/fustrate/_module.coffee
@@ -149,99 +149,6 @@
       .map (line) -> Fustrate.escapeHtml(line)
       .join '<br />'

-# Replicate a few common prototype methods on String and Array
-String::titleize = ->
-  @replace(/_/g, ' ').replace /\b[a-z]/g, (char) -> char.toUpperCase()
-
-String::capitalize = ->
-  @charAt(0).toUpperCase() + @slice(1)
-
-String::phoneFormat = ->
-  if /^(\d+)(ext|x)(\d+)$/.test @
-    @replace /(\d+)(ext|x)(\d+)/, ->
-      arguments[1].phoneFormat() + ' x' + arguments[3]
-  else if /^1\d{10}$/.test @
-    @replace /1(\d{3})(\d{3})(\d{4})/, '1 ($1) $2-$3'
-  else if /^\d{10}$/.test @
-    @replace /(\d{3})(\d{3})(\d{4})/, '($1) $2-$3'
-  else if /^\d{7}$/.test @
-    @replace /(\d{3})(\d{4})/, '$1-$2'
-  else
-    @
-
-String::dasherize = ->
-  @replace /_/g, '-'
-
-Array::compact = (strings = true) ->
-  @forEach (element, index) =>
-    @splice(index, 1) if element is undefined or (strings and element is '')
-
-  @
-
-Array::toSentence = ->
-  switch @length
-    when 0 then ''
-    when 1 then @[0]
-    when 2 then "#{@[0]} and #{@[1]}"
-    else "#{@slice(0, -1).join(', ')}, and #{@[@length - 1]}"
-
-Array::remove = (object) ->
-  index = @indexOf object
-  @splice index, 1 if index isnt -1
-
-Array::first = ->
-  @[0]
-
-Array::last = ->
-  @[@length - 1]
-
-Array::peek = Array::last
-
-# Used to define getters and setters
-Function::define = (name, methods) ->
-  Object.defineProperty @::, name, methods
-
-Function::debounce = (delay = 250) ->
-  timeout = null
-  self = @
-
-  (args...) ->
-    context = @
-
-    delayedFunc = ->
-      self.apply(context, args)
-      timeout = null
-
-    clearTimeout(timeout) if timeout
-
-    timeout = setTimeout delayedFunc, delay
-
-Object.defineProperty Object.prototype, 'tap',
-  enumerable: false
-  value: (func) ->
-    if typeof func is 'function'
-      func.apply(@)
-    else
-      @[func].apply(@, Array::slice.call(arguments).slice(1))
-
-    @
-
-Number::bytesToString = ->
-  return "#{@} B" if @ < 1000
-
-  return "#{(@ / 1000).toFixed(2).replace(/[0.]+$/, '')} kB" if @ < 1000000
-
-  if @ < 1000000000
-    return "#{(@ / 1000000).toFixed(2).replace(/[0.]+$/, '')} MB"
-
-  "#{(@ / 1000000000).toFixed(2).replace(/[0.]+$/, '')} GB"
-
-String::isBlank = ->
-  @.trim() is ''
-
-String::strip = ->
-  @.replace(/^\s+|\s+$/g, '')
-
 jQuery.fn.outerHTML = ->
   return '' unless @length


save some fetches when query is empty

save some fetches when query is empty

--- vue/src/components/lmo_textarea/mentioning.coffee
+++ vue/src/components/lmo_textarea/mentioning.coffee
@@ -13,6 +13,7 @@

   mounted: ->
     @fetchMentionable = debounce ->
+      return unless @query
       @fetchingMentions = true
       Records.users.fetchMentionable(@query, @model).then (response) =>
         @fetchingMentions = false


Fix fileBlacklist cache to work with path changes

--- lib/autocomplete-manager.coffee
+++ lib/autocomplete-manager.coffee
@@ -87,6 +87,8 @@
     # Subscribe to editor events:
     # Close the overlay when the cursor moved without changing any text
     @editorSubscriptions.add(@editor.onDidChangeCursorPosition(@cursorMoved))
+    @editorSubscriptions.add @editor.onDidChangePath =>
+      @isCurrentFileBlackListedCache = null

   paneItemIsValid: (paneItem) ->
     return false unless paneItem?


:racehorse: Scope observed config by namespace

This hits performance when many default configs loaded,
e.g. when package with a lot of config keys is activated.

--- lib/auto-update-packages.coffee
+++ lib/auto-update-packages.coffee
@@ -38,7 +38,7 @@
       @updatePackagesIfAutoUpdateBlockIsExpired()
     , @getAutoUpdateCheckInterval()

-    @configSubscription = atom.config.onDidChange =>
+    @configSubscription = atom.config.onDidChange NAMESPACE, =>
       @disableAutoUpdate()
       @enableAutoUpdate()


Add docstrings to simulation.coffee

Add docstrings to simulation.coffee

--- js/simulation.coffee
+++ js/simulation.coffee
@@ -392,6 +392,8 @@
     addf: the content of the card
     type: the type of the data either image or youtube
     tag: the tag element to append to
+    with_div: function to add an extra div for columns
+    take_func: data preprocessing function before bind

   Returns:
     a function to build the card
@@ -404,6 +406,14 @@

 update_data = (x) ->
+  ### Update chart data depending on name of the data
+
+  Args:
+    x: the data to update
+
+  Returns:
+    the updated data
+  ###
   if x.data[0].name == "free_energy"
     x.axes[0].title = "Time"
     x.axes[1].title = "Free Energy"
@@ -423,12 +433,22 @@

 add_src = (x) ->
+  ### Extract Vega chart as SVG url
+
+  Args:
+    x: the img selection to add the Vega src to
+  ###
   new vega.View(vega.parse(x.datum()))
     .toImageURL("svg")
     .then((url) -> x.attr("src", url))

 add_chart = (x) ->
+  ### Build a div for a card with a Vega chart
+
+  Args:
+    x: the selection
+  ###
   add_src(add_card_image_(x)
     .attr("style", "backgroud-color: white;"))

@@ -440,6 +460,13 @@

 add_card_col = (addf, type, take_func) ->
+  ### Construct a data card in columns and rows for #images
+
+  Args:
+    addf: the content of the card
+    type: the type of the data either image, line or youtube
+    take_func: the data preprocessing function
+  ###
   with_div = (x) -> x.append("div").attr("class", "col s4")
   add_card(addf, type, "#images", with_div=with_div, take_func=take_func)


avoid a possible edge case issue

avoid a possible edge case issue

--- src/smc-util/syncstring.coffee
+++ src/smc-util/syncstring.coffee
@@ -808,7 +808,7 @@
             return
         if min_age_m > 0
             # if min_age_m is 0 always do it immediately; if > 0 check what it was:
-            last_active = @_syncstring_table?.get_one().get('last_active')
+            last_active = @_syncstring_table?.get_one()?.get('last_active')
             # if not defined or not set recently, do it.
             if not (not last_active? or +last_active <= +misc.server_minutes_ago(min_age_m))
                 return


--- src/smc-util/syncstring.coffee
+++ src/smc-util/syncstring.coffee
@@ -1795,8 +1795,13 @@
         # compute result of applying all patches in order to snapshot
         new_remote = @_patch_list.value()

-        # if document changed, set to new version
-        if not @_doc?.is_equal(new_remote)
+        # temporary hotfix for https://github.com/sagemathinc/smc/issues/1873
+        try
+            changed = not @_doc?.is_equal(new_remote)
+        catch
+            changed = true
+        # if any possibility that document changed, set to new version
+        if changed
             @_last = @_doc = new_remote
             @emit('change')


Fix typo

Fix typo

--- lib/createServiceFilters.coffee
+++ lib/createServiceFilters.coffee
@@ -31,7 +31,7 @@
         else
           next()

-  # check existance
+  # check existence
   addTo stack, generateFilter "#{name}.exists", (args, next) ->

     # only check if it's not present but required


Set active and inactive in the results pane

--- lib/project/results-pane.coffee
+++ lib/project/results-pane.coffee
@@ -30,8 +30,13 @@
     @loadingMessage.hide()

     @model = @constructor.model
+    @model.setActive(true)
+
     @handleEvents()
     @onFinishedSearching(@model.getResultsSummary())
+
+  beforeRemove: ->
+    @model.setActive(false)

   getPane: ->
     @parent('.item-views').parent('.pane').view()


fix whitespace

fix whitespace

--- lib/python-debugger-view.coffee
+++ lib/python-debugger-view.coffee
@@ -80,7 +80,7 @@

   # Extract the file name and line number output by the debugger.
   processDebuggerOutput: (data) ->
-    data_str = data.toString().trim()
+    data_str = data.toString().trim()
     lineNumber = null
     fileName = null


more verbose name for a class

more verbose name for a class

--- library/exposure/watched.coffee
+++ library/exposure/watched.coffee
@@ -46,7 +46,7 @@
 # the duplexed service life cycle, by issuing a visual popup style
 # notifications when most notable events occure on the service. It
 # reports connections events in additions to the exceptional ones.
-module.exports.Watched = class Watched extends Duplex
+module.exports.WatchedDuplex = class WatchedDuplex extends Duplex

     # This is a marker that indicates to some internal subsystems
     # that this class has to be considered abstract and therefore


remove ugly stack when cast is not handled

--- src/utils/type_checking.coffee
+++ src/utils/type_checking.coffee
@@ -97,8 +97,7 @@

     if reportCastIgnore
         for own prop, value of raw when prop not in handled
-            log.warn "Warning : cast ignored property '#{prop}'", \
-                     raw, new Error().stack
+            log.warn "Warning : cast ignored property '#{prop}'"

     return target


backend_pubsub: notificationPush updates include sender & actor

--- src/xmpp/backend_pubsub.coffee
+++ src/xmpp/backend_pubsub.coffee
@@ -196,6 +196,8 @@
                 cb not err and valid
         , (updates) =>
             if updates? and updates.length > 0
+                updates.sender = sender
+                updates.actor = sender
                 # Apply pushed updates
                 @emit 'notificationPush', updates
         )


server calls now async

server calls now async

--- static/dashboard/js/app.coffee
+++ static/dashboard/js/app.coffee
@@ -10,6 +10,9 @@
       'views/ContentView'
       'text!templates/overview.html'
       'text!templates/sidebar.html'
+      'views/chartView'
+      'models/ChartModel'
+      'highcharts'
     ],
     (
       $
@@ -22,6 +25,9 @@
       ContentView
       OverviewTemplate
       SideBarTemplate
+      ChartView
+      ChartModel
+      Highcharts
     ) ->

       # Prevent links from reloading the page
@@ -32,9 +38,17 @@
       pushstateClick: (event) ->
         event.preventDefault()

+
       initialize: ->
         #  Pass in our Router module and call it's initialize function
         Router.initialize()
+
+        # Notify user that server is between states.
+        $('#server_off').on "click", ->
+          $('#server_status').css "color": "yellow"
+
+        $('#server_on').on "click", ->
+          $('#server_status').css "color": "yellow"

         # Listen for server status via socket.io
         $ ->
@@ -42,7 +56,6 @@
           socket.on "connect", ->
             $.ajax
               url: "/monitor_server"
-              async: false
           socket.on 'status', (data) ->
             if parseInt(data) is 0
               $('#server_status').css "color": "green"
@@ -58,27 +71,29 @@

         # Load at-a-glance model and data
         ataglance = new AtAGlanceModel
-        ataglance.fetch async: false
-
-        # Load configuration model
-        config = new ConfigModel
-        config.fetch async: false
-
-        # Load and add content html
-        overviewContentHTML = _.template(OverviewTemplate,
-          input:
-            balance: ataglance.get("balance")
-            debug: if config.get("Server Parameters").debug is "True" then "checked" else ""
-            using_sandbox: if config.get("HIT Configuration").using_sandbox is "True" then "checked" else "")
-        $('#content').html(overviewContentHTML)
-
-        # Load and add side bar html
-        sideBarHTML = _.template(SideBarTemplate)
-        $('#sidebar').html(sideBarHTML)
-        sidebarView = new SidebarView(
-          config: config
-          ataglance: ataglance)
-        sidebarView.initialize()
+        atAGlancePromise = ataglance.fetch()
+        atAGlancePromise.done(->
+          # Load configuration model
+          config = new ConfigModel
+          configPromise = config.fetch()
+          configPromise.done(->
+            overview = _.template(OverviewTemplate,
+              input:
+                balance: ataglance.get("balance")
+                debug: if config.get("Server Parameters").debug is "True" then "checked" else ""
+                using_sandbox: if config.get("HIT Configuration").using_sandbox is "True" then "checked" else "")
+            $('#content').html(overview)
+            chrtView = new ChartView()
+            chrtView.initialize()
+            chrtView.setChart()
+            # Load and add side bar html
+            sideBarHTML = _.template(SideBarTemplate)
+            $('#sidebar').html(sideBarHTML)
+            sidebarView = new SidebarView(
+              config: config
+              ataglance: ataglance
+              chart: chrtView)
+            sidebarView.initialize()))

         # Load content view after html; req's ids to be present
         contentView = new ContentView()
@@ -90,13 +105,9 @@

         # Shutdown button
         $("#server_off").on "click", ->
-          url = config.get("HIT Configuration").question_url + '/shutdown'
-          url_pattern =  /^https?\:\/\/([^\/:?#]+)(?:[\/:?#]|$)/i
-          domain = url.match(url_pattern)[0] + config.get("Server Parameters").port + '/shutdown'
           $.ajax
-            url: domain
+            url: '/shutdown'
             type: "GET"
-            data: {hash: config.get("Server Parameters").hash}

         # Run button
         $("#server_on").on "click", ->
@@ -109,7 +120,6 @@
                 socket.on "connect", ->
                   $.ajax
                     url: "/monitor_server"
-                    # async: false
                 socket.on 'status', (data) ->
                   if parseInt(data) is 0
                     $('#server_status').css "color": "green"


remove legacy tests, need to write new

remove legacy tests, need to write new

--- test/contextFactorySpec.coffee
+++ test/contextFactorySpec.coffee
@@ -21,7 +21,7 @@
         subject = new ContextFactory(null, null, JasmineStore)

       it 'interface', ->
-        expect(subject).toHaveProperties ['is']
+        expect(subject).toHaveProperties ['is', 'defined', 'value']

       describe '#is', ->
         fn             =
@@ -36,96 +36,25 @@
           name    = 'collection'
           store   = {}
           factory = new ContextFactory(name, store, JasmineStore)
-          beforeEachArgs = /return eval\.call\(this, "var " \+ prop \+ " = '" \+ Store\[prop\] \+ "';"\);/
-          afterEachArgs1 = /delete Store\[prop\];/
-          afterEachArgs2 = /return eval\.call\(this, "delete " \+ prop \+ ";"\);/

-        describe 'first - plain val', ->
+        xdescribe 'first - plain val', ->

           beforeEach -> factory.is 'something'

-          it 'should add new key to store', ->
-            expect(store).toHaveProperties [name]
-
-          it 'should assign proper value to key in store', ->
-            expect(store[name]).toEqual 'something'
-
-          it 'should call jasmine\'s beforeEach method', ->
-            expect(JasmineStore.instance.beforeEach).toHaveBeenCalled()
-            expect(JasmineStore.instance.beforeEach.calls.count()).toBe 1
-            expect(JasmineStore.instance.beforeEach.calls.argsFor(0)).toMatch beforeEachArgs
-
-          it 'should call jasmine\'s afterEach method', ->
-            expect(JasmineStore.instance.afterEach).toHaveBeenCalled()
-            expect(JasmineStore.instance.afterEach.calls.count()).toBe 1
-            expect(JasmineStore.instance.afterEach.calls.argsFor(0)).toMatch afterEachArgs1
-            expect(JasmineStore.instance.afterEach.calls.argsFor(0)).toMatch afterEachArgs2
-
-        describe 'first - function', ->
+        xdescribe 'first - function', ->

           beforeEach ->
             fn = -> 'function result'
             factory.is fn

-          it 'should add new key to store', ->
-            expect(store).toHaveProperties [name]
-
-          it 'should assign function to key in store', ->
-            expect(store[name]).toBe fn
-
-          it 'should call jasmine\'s beforeEach method', ->
-            expect(JasmineStore.instance.beforeEach).toHaveBeenCalled()
-            expect(JasmineStore.instance.beforeEach.calls.count()).toBe 1
-            expect(JasmineStore.instance.beforeEach.calls.argsFor(0)).toMatch beforeEachArgs
-
-          it 'should call jasmine\'s afterEach method', ->
-            expect(JasmineStore.instance.afterEach).toHaveBeenCalled()
-            expect(JasmineStore.instance.afterEach.calls.count()).toBe 1
-            expect(JasmineStore.instance.afterEach.calls.argsFor(0)).toMatch afterEachArgs1
-            expect(JasmineStore.instance.afterEach.calls.argsFor(0)).toMatch afterEachArgs2
-
-        describe 'first - plain val, second - function ', ->
+        xdescribe 'first - plain val, second - function ', ->

           beforeEach ->
             fn = -> 'function result'
             factory.is 'something', fn

-          it 'should add new key to store', ->
-            expect(store).toHaveProperties [name]
-
-          it 'should assign proper value to key in store', ->
-            expect(store[name]).toEqual 'something'
-
-          it 'should call jasmine\'s beforeEach method', ->
-            expect(JasmineStore.instance.beforeEach).toHaveBeenCalled()
-            expect(JasmineStore.instance.beforeEach.calls.count()).toBe 1
-            expect(JasmineStore.instance.beforeEach.calls.argsFor(0)).toMatch beforeEachArgs
-
-          it 'should call jasmine\'s afterEach method', ->
-            expect(JasmineStore.instance.afterEach).toHaveBeenCalled()
-            expect(JasmineStore.instance.afterEach.calls.count()).toBe 1
-            expect(JasmineStore.instance.afterEach.calls.argsFor(0)).toMatch afterEachArgs1
-            expect(JasmineStore.instance.afterEach.calls.argsFor(0)).toMatch afterEachArgs2
-
-        describe 'first - function, second - plain val', ->
+        xdescribe 'first - function, second - plain val', ->

           beforeEach ->
             fn = -> 'function result'
             factory.is fn, 'something'
-
-          it 'should add new key to store', ->
-            expect(store).toHaveProperties [name]
-
-          it 'should assign function to key in store', ->
-            expect(store[name]).toBe fn
-
-          it 'should call jasmine\'s beforeEach method', ->
-            expect(JasmineStore.instance.beforeEach).toHaveBeenCalled()
-            expect(JasmineStore.instance.beforeEach.calls.count()).toBe 1
-            expect(JasmineStore.instance.beforeEach.calls.argsFor(0)).toMatch beforeEachArgs
-
-          it 'should call jasmine\'s afterEach method', ->
-            expect(JasmineStore.instance.afterEach).toHaveBeenCalled()
-            expect(JasmineStore.instance.afterEach.calls.count()).toBe 1
-            expect(JasmineStore.instance.afterEach.calls.argsFor(0)).toMatch afterEachArgs1
-            expect(JasmineStore.instance.afterEach.calls.argsFor(0)).toMatch afterEachArgs2


Fix test according to changes in smtp mailer.

--- test/mailers/mailerTest.coffee
+++ test/mailers/mailerTest.coffee
@@ -25,6 +25,6 @@
         message.to.should.equal "user@company.tld"
         message.from.should.equal "root@localhost"
         message.text.should.equal "text1"
-        message.html.should.equal "html1"
+        message.attachment[0].data.should.equal "html1"

-        done()+        done()


Trying travis-ci with base64 strings instead of buffer compare

--- test/specs/uploads/read.coffee
+++ test/specs/uploads/read.coffee
@@ -1,33 +1,28 @@
 describe 'Uploads GET show', ->

   it 'should get a resized image by width', (done) ->
-    resized = fs.readFileSync("#{@test_dir}/support/images/image_w_100.jpg")
+    resized = fs.readFileSync("#{@test_dir}/support/images/image_w_100.jpg").toString('base64')
     buffers = []
     stream  = request.get('http://localhost:3001/uploads/images/image?w=100')
     stream.on 'data', (data) -> buffers.push(data)
     stream.on 'end', ->
-      console.log "========="
-      buffer = Buffer.concat(buffers)
-      console.log buffer
-      expect(buffer).to.deep.equal resized
+      expect(resized).to.equal Buffer.concat(buffers).toString('base64')
       done()

   it 'should get a resized image by height', (done) ->
-    resized = fs.readFileSync("#{@test_dir}/support/images/image_h_100.jpg")
+    resized = fs.readFileSync("#{@test_dir}/support/images/image_h_100.jpg").toString('base64')
     buffers = []
     stream  = request.get('http://localhost:3001/uploads/images/image?h=100')
     stream.on 'data', (data) -> buffers.push(data)
     stream.on 'end', ->
-      buffer = Buffer.concat(buffers)
-      expect(buffer).to.deep.equal resized
+      expect(resized).to.equal Buffer.concat(buffers).toString('base64')
       done()

   it 'should get a cropped and resized image', (done) ->
-    resized = fs.readFileSync("#{@test_dir}/support/images/image_crop_50.jpg")
+    resized = fs.readFileSync("#{@test_dir}/support/images/image_crop_50.jpg").toString('base64')
     buffers = []
     stream  = request.get('http://localhost:3001/uploads/images/image?w=50&h=50&m=crop')
     stream.on 'data', (data) -> buffers.push(data)
     stream.on 'end', ->
-      buffer = Buffer.concat(buffers)
-      expect(buffer).to.deep.equal resized
+      expect(resized).to.equal Buffer.concat(buffers).toString('base64')
       done()


Fire more of the appropriate events when filling form inputs.

--- application/chrome/content/wesabe/dom/page.coffee
+++ application/chrome/content/wesabe/dom/page.coffee
@@ -12,12 +12,19 @@
 #
 wesabe.dom.page =
   EVENT_TYPE_MAP:
-    click: 'MouseEvents'
+    click:     'MouseEvents'
     mousedown: 'MouseEvents'
-    mouseup: 'MouseEvents'
+    mouseup:   'MouseEvents'
     mousemove: 'MouseEvents'
-    change: 'HTMLEvents'
-    submit: 'HTMLEvents'
+    change:    'HTMLEvents'
+    submit:    'HTMLEvents'
+    keydown:   'KeyEvents'
+    keypress:  'KeyEvents'
+    keyup:     'KeyEvents'
+    focusin:   'UIEvents'
+    focus:     'UIEvents'
+    blur:      'UIEvents'
+    focusout:  'UIEvents'

   #
   # Finds the first node matching +xpathOrNode+ in +document+ with optional
@@ -100,29 +107,47 @@
   # @public
   #
   fill: (document, xpathOrNode, valueOrXpathOrNode) ->
-    wesabe.tryThrow('page.fill', (log) =>
-      element = wesabe.dom.page.findStrict(document, xpathOrNode)
-      log.info('element=', element)
+    wesabe.tryThrow 'page.fill', (log) =>
+      element = wesabe.untaint(@findStrict(document, xpathOrNode))
+      log.info 'element=', wesabe.taint(element)

       value = wesabe.untaint(valueOrXpathOrNode)
       value = value.toString() if wesabe.isNumber(value)

-      if value && !wesabe.isString(value)
-        valueNode = @findStrict(document, value, element)
-        log.debug('valueNode=', valueNode)
-        value = wesabe.untaint(valueNode.value)
-
-      log.radioactive('value=', value)
-
-      maxlength = wesabe.untaint(element).getAttribute("maxlength")
-      if value && maxlength
-        maxlength = parseInt(maxlength, 10)
+      if value and not wesabe.isString(value)
+        valueNode = wesabe.untaint(@findStrict(document, value, element))
+        log.debug 'valueNode=', wesabe.taint(valueNode)
+        value = valueNode.value
+
+      log.radioactive 'value=', value
+
+      maxlength = element.getAttribute("maxlength")
+      if value and maxlength
+        maxlength = wesabe.lang.number.parse(maxlength)
         if maxlength
-          log.warn("Truncating value to ", maxlength, " characters")
+          log.warn "Truncating value to ", maxlength, " characters"
           value = value[0...maxlength]

-      wesabe.untaint(element).value = value
-      @fireEvent(document, element, 'change'))
+      ##
+      #
+      # Fill Event Order
+      #
+      # focusin
+      # focus
+      # change
+      # focusout
+      # blur
+      #
+      ##
+
+      @fireEvent document, element, 'focusin'
+      @fireEvent document, element, 'focus'
+
+      element.value = value
+      @fireEvent document, element, 'change'
+
+      @fireEvent document, element, 'focusout'
+      @fireEvent document, element, 'blur'

   #
   # Clicks the given node/xpath endpoint.
@@ -140,12 +165,12 @@
   # @public
   #
   click: (document, xpathOrNode) ->
-    wesabe.tryThrow('page.click', (log) =>
-      element = @findStrict(document, xpathOrNode)
-      log.info('element=', element)
-      @fireEvent(document, element, 'mousedown')
-      @fireEvent(document, element, 'click')
-      @fireEvent(document, element, 'mouseup'))
+    wesabe.tryThrow 'page.click', (log) =>
+      element = @findStrict document, xpathOrNode
+      log.info 'element=', element
+      @fireEvent document, element, 'mousedown'
+      @fireEvent document, element, 'click'
+      @fireEvent document, element, 'mouseup'

   #
   # Checks the element given by +xpathOrNode+.
@@ -201,14 +226,14 @@
   # @public
   #
   submit: (document, xpathOrNode) ->
-    wesabe.tryThrow('page.submit', (log) =>
-      element = @findStrict(document, xpathOrNode)
-      log.info('element=', element)
+    wesabe.tryThrow 'page.submit', (log) =>
+      element = @findStrict document, xpathOrNode
+      log.info 'element=', element
       # find the containing form
-      element = element.parentNode while element && element.tagName.toLowerCase() != 'form'
+      element = element.parentNode while element and element.tagName.toLowerCase() isnt 'form'
       throw new Error('No form found wrapping element! Cannot submit') unless element

-      @fireEvent(document, element, 'submit'))
+      @fireEvent document, element, 'submit'

   #
   # Fires an event on the given node/xpath endpoint.
@@ -220,10 +245,10 @@
   #
   # @public
   #
-  fireEvent: (document, xpathOrNode, type) ->
+  fireEvent: (document, xpathOrNode, type, args...) ->
     element = wesabe.untaint(@findStrict(document, xpathOrNode))
     event = element.ownerDocument.createEvent(@EVENT_TYPE_MAP[type])
-    event.initEvent(type, true, true)
+    event.initEvent(type, true, true, args...)
     element.dispatchEvent(event)

   #


solar graph uses example options...

solar graph uses example options...

trying to debug production js, so I'm switching out various parts of buggy code with working code. JS currently breaks on 'graph.draw(data, options)'

--- app/views/pv_queries/create.js.coffee
+++ app/views/pv_queries/create.js.coffee
@@ -24,14 +24,14 @@

     options =
       width:  "500px"
-      height: "450px"
+      height: "400px"
       style: "surface"
       showPerspective: true
       showGrid: true
       showShadow: false
       keepAspectRatio: true
-      verticalRatio: 0.8
-      cameraPosition: {"horizontal": 5.6, "vertical": 0.2, "distance": 1.8}
+      verticalRatio: 0.5
+

     alert 'before instantiating graph'
     # Instantiate our graph object.


Remove handler for event that no longer fires

--- lib/bracket-matcher-view.coffee
+++ lib/bracket-matcher-view.coffee
@@ -43,9 +43,6 @@
       if @updateHighlights
         @updateHighlights = false
         @updateMatch()
-
-    @subscribe @editorView, 'editor:min-width-changed', =>
-      @updateHighlights = true if @editor.getSoftWrap()

     @subscribe @editor, 'soft-wrap-changed', =>
       @updateHighlights = true


Fixed small bug when checking for includes

--- tasks/coffee_build.coffee
+++ tasks/coffee_build.coffee
@@ -368,10 +368,8 @@
   include = options.include or []
   depAliases = {}

-  for inc in options.include
-    if inc.alias
-      if not inc.global
-        throw new Error("specify a global property for #{inc.alias}")
+  for inc in include
+    if inc.alias and inc.global
       depAliases[inc.alias] = inc.global

   depAliases = JSON.stringify(depAliases)


Eliminate UrlHelper

Eliminate UrlHelper

This isn't used anywhere so removing.

--- elements/UrlHelper.coffee
+++ elements/UrlHelper.coffee
@@ -1,11 +0,0 @@
-
-module.exports =
-  cleanUrl: (url) ->
-      url.indexOf('http') is 0
-        url
-      else if url.indexOf('/assets')
-        # todo include local folder if not deployed in domain root
-        url
-      else
-        # Assume it's local and we need to add the /assets/
-        '/assets/' + url


Small fix

Small fix

--- views/services/layout.cjsx
+++ views/services/layout.cjsx
@@ -112,7 +112,7 @@
         if (alert == null || typeof(alert) == 'undefined')
           return;
         alert.style.zoom = 0.8;
-        alert.style.left = '200px';
+        alert.style.left = '225px';
         alert.style.top = '0px';
       }, 500);
     }


Start running poll whenever isSyncing changes

This ensures that timing is not important, ie. if user data is refreshed
later, we can still start running poll() if user is syncing.

--- assets/scripts/app/models/user.coffee
+++ assets/scripts/app/models/user.coffee
@@ -20,11 +20,10 @@
       value
   ).property('login', '_name')

-  init: ->
-    @_super()
-
+  isSyncingDidChange: (->
     Ember.run.next this, ->
       @poll() if @get('isSyncing')
+  ).observes('isSyncing')

   urlGithub: (->
     "https://github.com/#{@get('login')}"
@@ -66,7 +65,6 @@
     self = this
     Travis.ajax.post('/users/sync', {}, ->
       self.setWithSession('isSyncing', true)
-      self.poll()
     )

   poll: ->


use /dev/csrf for smoke tests

use /dev/csrf for smoke tests

--- services/web/test/smoke/coffee/SmokeTests.coffee
+++ services/web/test/smoke/coffee/SmokeTests.coffee
@@ -33,17 +33,13 @@
 				return done(err)
 			logger.log "smoke test: clearing rate limit "
 			require("../../../app/js/infrastructure/RateLimiter.js").clearRateLimit "open-project", "#{Settings.smokeTest.projectId}:#{Settings.smokeTest.userId}", ->
-				logger.log "smoke test: hitting /login"
+				logger.log "smoke test: hitting dev/csrf"
 				command =  """
-					curl -H  "X-Forwarded-Proto: https" -c #{cookeFilePath} #{buildUrl('login')}
+					curl -H  "X-Forwarded-Proto: https" -c #{cookeFilePath} #{buildUrl('dev/csrf')}
 				"""
 				child.exec command, (err, stdout, stderr)->
 					if err? then done(err)
-					csrfMatches = stdout.match("<input name=\"_csrf\" type=\"hidden\" value=\"(.*?)\">")
-					if !csrfMatches?
-						logger.err stdout:stdout, "smoke test: does not have csrf token"
-						return done("smoke test: does not have csrf token")
-					csrf = csrfMatches[1]
+					csrf = stdout
 					logger.log "smoke test: converting cookie file 1"
 					convertCookieFile (err) ->
 						return done(err) if err?


Use correct 64-bith arch

Use correct 64-bith arch

--- build/tasks/publish-build-task.coffee
+++ build/tasks/publish-build-task.coffee
@@ -84,7 +84,7 @@
         if process.arch is 'ia32'
           arch = 'i386'
         else
-          arch = 'x64_64'
+          arch = 'x86_64'
         assetName = "atom.#{arch}.rpm"

       {cp} = require('./task-helpers')(grunt)


remove tooltipDisposable

remove tooltipDisposable

--- lib/selection-count-view.coffee
+++ lib/selection-count-view.coffee
@@ -1,7 +1,6 @@
 _ = require 'underscore-plus'

 class SelectionCountView extends HTMLElement
-  toolTipDisposable: null

   initialize: ->
     @classList.add('selection-count', 'inline-block')


Make sure that Gatsby lib js/jsx code is compiled

Fixes #43

--- lib/utils/webpack.config.coffee
+++ lib/utils/webpack.config.coffee
@@ -1,5 +1,20 @@
 webpack = require 'webpack'
 StaticSiteGeneratorPlugin = require 'static-site-generator-webpack-plugin'
+
+gatsbyLib = /(gatsby.lib)/i
+libDirs = /(node_modules|bower_components)/i
+babelExcludeTest = (absPath) ->
+  result = false
+  # There is a match, don't exclude this file.
+  if absPath.match(gatsbyLib) isnt null
+    result = false
+  # There is a match, do exclude this file.
+  else if absPath.match(libDirs) isnt null
+    result = true
+  else
+    result = false
+
+  return result

 module.exports = (program, directory, stage, webpackPort = 1500, routes=[]) ->
   output = ->
@@ -82,12 +97,12 @@
           { test: /\.cjsx$/, loaders: ['react-hot', 'coffee', 'cjsx']},
           {
             test: /\.jsx?$/,
-            exclude: /(node_modules|bower_components)/,
+            exclude: babelExcludeTest
             loaders: ['react-hot', 'babel']
           }
           {
             test: /\.js?$/,
-            exclude: /(node_modules|bower_components)/,
+            exclude: babelExcludeTest
             loaders: ['react-hot', 'babel']
           }
           { test: /\.less/, loaders: ['style', 'css', 'less']},
@@ -112,12 +127,12 @@
           { test: /\.cjsx$/, loaders: ['coffee', 'cjsx']},
           {
             test: /\.jsx?$/,
-            exclude: /(node_modules|bower_components)/,
+            exclude: babelExcludeTest
             loaders: ['babel']
           }
           {
             test: /\.js?$/,
-            exclude: /(node_modules|bower_components)/,
+            exclude: babelExcludeTest
             loaders: ['babel']
           }
           { test: /\.less/, loaders: ['css', 'less']},
@@ -142,12 +157,12 @@
           { test: /\.cjsx$/, loaders: ['coffee', 'cjsx']},
           {
             test: /\.jsx?$/,
-            exclude: /(node_modules|bower_components)/,
+            exclude: babelExcludeTest
             loaders: ['babel']
           }
           {
             test: /\.js?$/,
-            exclude: /(node_modules|bower_components)/,
+            exclude: babelExcludeTest
             loaders: ['babel']
           }
           { test: /\.less/, loaders: ['style', 'css', 'less']},


add server info to the ev container

add server info to the ev container

--- library/exposure/central.coffee
+++ library/exposure/central.coffee
@@ -110,6 +110,7 @@
         assert _.isString packed.hostname = os.hostname()
         assert _.isString packed.platform = os.platform()
         assert _.isString packed.scope = @kernel.scope.tag
+        assert _.isObject packed.server = nconf.get "server"
         assert cn = nconf.get("central:collection") or null
         i = packed.identica = @kernel.constructor.identica()
         o = try nconf.get "central:options" or new Object()


Fixed some comments in the handle specs.

--- spec/draggable_spec.coffee
+++ spec/draggable_spec.coffee
@@ -526,7 +526,7 @@
             # The draggable's start position
             @start = @$draggable.offset()

-            # Drag the draggable a standard distance, using the handle
+            # Drag the draggable a standard distance, using a descendant of the handle
             @handle.children().simulate 'drag',
               dx: options.dragDistance
               dy: options.dragDistance
@@ -544,7 +544,7 @@
             # The draggable's start position
             @start = @$draggable.offset()

-            # Drag the draggable a standard distance, using the handle
+            # Drag the draggable a standard distance
             @$draggable.simulate 'drag',
               dx: options.dragDistance
               dy: options.dragDistance


Remove jQuery usage from find-view-spec

Remove jQuery usage from find-view-spec

--- spec/find-view-spec.coffee
+++ spec/find-view-spec.coffee
@@ -1,4 +1,3 @@
-{$} = require 'atom-space-pen-views'
 path = require 'path'

 describe 'FindView', ->
@@ -543,9 +542,7 @@
       expect(editor.getSelectedBufferRange()).toEqual [[2, 34], [2, 39]]

     it "selects the previous match when the next match button is pressed while holding shift", ->
-      shiftClick = $.Event('click')
-      shiftClick.shiftKey = true
-      findView.nextButton.trigger shiftClick
+      findView.nextButton[0].dispatchEvent(new MouseEvent('click', {shiftKey: true}))
       expect(findView.resultCounter.text()).toEqual('1 of 6')
       expect(editor.getSelectedBufferRange()).toEqual [[1, 22], [1, 27]]

@@ -1292,7 +1289,7 @@

       describe "when the replace next button is pressed", ->
         it "replaces the match after the cursor and selects the next match", ->
-          $('.find-and-replace .btn-next').click()
+          findView[0].querySelector('.btn-next').click()
           expect(findView.resultCounter.text()).toEqual('2 of 5')
           expect(editor.lineTextForBufferRow(2)).toBe "    if (cats.length <= 1) return items;"
           expect(editor.getSelectedBufferRange()).toEqual [[2, 33], [2, 38]]
@@ -1317,14 +1314,14 @@
     describe "replace all", ->
       describe "when the replace all button is pressed", ->
         it "replaces all matched text", ->
-          $('.find-and-replace .btn-all').click()
+          findView[0].querySelector('.btn-all').click()
           expect(findView.resultCounter.text()).toEqual('no results')
           expect(editor.getText()).not.toMatch /items/
           expect(editor.getText().match(/\bcats\b/g)).toHaveLength 6
           expect(editor.getSelectedBufferRange()).toEqual [[2, 0], [2, 0]]

         it "all changes are undoable in one transaction", ->
-          $('.find-and-replace .btn-all').click()
+          findView[0].querySelector('.btn-all').click()
           editor.undo()
           expect(editor.getText()).not.toMatch /\bcats\b/g


Changes to convention for consistency

Changes to convention for consistency

--- steam.coffee
+++ steam.coffee
@@ -16,6 +16,9 @@
 #
 # Author:
 #   MrSaints
+#
+# Notes:
+#   * Refactor persistence

 moment = require 'moment'
 require 'ref'
@@ -61,8 +64,8 @@
     if not STEAM_API_KEY?
         return robot.logger.debug 'Missing STEAM_API_KEY in environment. Please set and try again.'

-    steamData = () ->
-        robot.brain.data.steam ?= {}
+    steamData = ->
+        robot.brain.data.steam or= {}

     robot.respond /steam id( me)? (.*)/i, (msg) ->
         getSteamID msg, msg.match[2], (id) ->


Added type checking

Added type checking

--- steam.coffee
+++ steam.coffee
@@ -103,6 +103,9 @@
                 params.account_id = id
                 history params, 'profile URL'
         else
+            if typeof value isnt 'number'
+                msg.reply "Invalid Steam ID entered. Please ensure you have included the -u flag if you are trying to search by profile URL."
+                return
             history params

     robot.respond /dota match (\d+)( \d*)?/i, (msg) ->


Visibility management and finding item index bug fixes

--- style.coffee
+++ style.coffee
@@ -174,6 +174,7 @@
 			globalShowDelay += showEvent.delay
 			showEvent.delay = 0

+		@item.visible = false
 		@file.readyToUse = false
 		stylesToRender.push @
 		updateWhenPossible @
@@ -188,6 +189,7 @@
 			unless utils.isEqual(classes.items(), @classes)
 				@classes = utils.clone(classes.items())

+		@item.visible = @visible
 		@item.document.node = @node
 		@item.document.visible = true
 		@updateText()
@@ -236,6 +238,7 @@

 		itemDocumentNode = @item.document.node
 		@item.document.node = null
+		@item.visible = false

 		tmpNode = @node
 		while tmpNode = tmpNode._parent
@@ -321,7 +324,7 @@
 		return

 	updateVisibility: ->
-		if @waiting or not @item
+		if @waiting or not @item or not @visible
 			return

 		visible = true
@@ -332,9 +335,7 @@
 			if not visible or not tmpNode or tmpNode.attrs.has('neft:style')
 				break

-		if @visible isnt visible
-			@visible = visible
-			@item.visible = visible
+		@item.visible = visible
 		return

 	ATTR_PRIMITIVE_VALUES =
@@ -487,6 +488,7 @@
 			return

 		if @item and @isAutoParent
+			@item.visible = @visible
 			@item.parent = null

 		if @item
@@ -548,6 +550,7 @@

 		@node._documentStyle = @
 		@node.style = @item
+		@visible = @item.visible

 		if @isLink()
 			@item.linkUri = @getLinkUri()
@@ -560,9 +563,10 @@

 	findItemWithParent = (item, parent) ->
 		tmp = item
-		while tmp = tmp._parent
-			if tmp is parent
+		while tmp and (tmpParent = tmp._parent)
+			if tmpParent is parent
 				return tmp
+			tmp = tmpParent
 		return

 	findItemIndex = (node, item, parent) ->
@@ -578,9 +582,10 @@

 			# tmpSiblingNode = tmpIndexNode
 			# while tmpSiblingNode = tmpSiblingNode._previousSibling
-			# 	if tmpSiblingNode._documentStyle?.item?.parent is parent
-			# 		item.index = tmpSiblingNode._documentStyle.item.index + 1
-			# 		return
+			# 	if tmpSiblingItem = tmpSiblingNode._documentStyle?.item
+			# 		if tmpSiblingTargetItem = findItemWithParent(tmpSiblingItem, parent)
+			# 			item.index = tmpSiblingTargetItem.index + 1
+			# 			return
 			if tmpIndexNode isnt node and tmpIndexNode.style
 				return
 			tmpIndexNode = tmpIndexNode._parent


:lipstick:

:lipstick:

--- spec/stdlib/fs-spec.coffee
+++ spec/stdlib/fs-spec.coffee
@@ -33,7 +33,6 @@
       expect(fs.exists(require.resolve("fixtures") + "/-nope-does-not-exist")).toBe false
       expect(fs.exists("")).toBe false
       expect(fs.exists(null)).toBe false
-      #expect(fs.exists(undefined)).toBe false

   describe ".join(paths...)", ->
     it "concatenates the given paths with the directory seperator", ->


Fixed a couple of bugs in AddObject method.

--- tasks.coffee
+++ tasks.coffee
@@ -118,12 +118,9 @@
           rootPath = ''
           ps = path.split('.')
           for p in ps
-            if root[p]?
-              root = root[p]
-              rootPath += "#{p}."
-            else
-              root = null
-              break
+            rootPath += "#{p}."
+            root = root[p]
+            break if not root?
         else
           root = device['InternetGatewayDevice']
           rootPath = 'InternetGatewayDevice.'
@@ -135,7 +132,9 @@
           parameters[p[0]] = 1 for p in methodResponse.parameterList
           findMissingParameters(root, parameters, rootPath, deviceUpdates.deletedObjects)
         else
-          deviceUpdates.deletedObjects = [rootPath.slice(0, -1)]
+          # avoid adding and deleting the same param
+          if rootPath isnt task.parameterPath
+            deviceUpdates.deletedObjects = [rootPath.slice(0, -1)]

       # some devices don't return the root object as described in the standard. add manually to update timestamp
       deviceUpdates.parameterNames.push([task.parameterPath]) if !!task.parameterPath
@@ -241,14 +240,16 @@
       when 'getParameterValues'
         this.getParameterValues(task.subtask, methodResponse, (err, status, cwmpResponse, deviceUpdates) =>
           common.extend(allDeviceUpdates, deviceUpdates)
-          if deviceUpdates and deviceUpdates.parameterValues
-            for p1 in deviceUpdates.parameterValues
-              for p2 in task.parameterValues
-                if common.endsWith(p1[0], ".#{p2[0]}")
-                  t = if p2[2] then p2[2] else p1[2]
-                  v = common.matchType(p1[1], p2[1])
-                  # TODO only include if writable
-                  task.appliedParameterValues.push([p1[0], v, t])
+          # if values are given, compare with default values
+          if task.parameterValues?
+            if deviceUpdates and deviceUpdates.parameterValues?
+              for p1 in deviceUpdates.parameterValues
+                for p2 in task.parameterValues
+                  if common.endsWith(p1[0], ".#{p2[0]}")
+                    t = if p2[2] then p2[2] else p1[2]
+                    v = common.matchType(p1[1], p2[1])
+                    # TODO only include if writable
+                    task.appliedParameterValues.push([p1[0], v, t])

           if methodResponse.faultcode?
             # Ignore GetParameterValues errors. A workaround for the crappy Seewon devices.


spec: Load url after event listener is set up

--- spec/asar-spec.coffee
+++ spec/asar-spec.coffee
@@ -450,10 +450,10 @@
       w = new BrowserWindow(show: false, width: 400, height: 400)
       p = path.resolve fixtures, 'asar', 'web.asar', 'index.html'
       u = url.format protocol: 'file', slashed: true, pathname: p
-      w.loadUrl u
       ipc.once 'dirname', (event, dirname) ->
         assert.equal dirname, path.dirname(p)
         done()
+      w.loadUrl u

     it 'loads script tag in html', (done) ->
       after ->


Add sub-workers of social api

Add sub-workers of social api

--- config/main.dev.coffee
+++ config/main.dev.coffee
@@ -475,6 +475,101 @@
             proxyPass   : "http://socialapi/sitemap$1.xml"
           }
         ]
+
+    topicfeed           :
+      group             : "socialapi"
+      supervisord       :
+        command         : "#{GOBIN}/topicfeed -c #{socialapi.configFilePath}"
+
+    realtime            :
+      group             : "socialapi"
+      supervisord       :
+        command         : "#{GOBIN}/realtime -c #{socialapi.configFilePath}"
+
+    populartopic        :
+      group             : "socialapi"
+      supervisord       :
+        command         : "#{GOBIN}/populartopic -c #{socialapi.configFilePath}"
+
+    popularpost         :
+      group             : "socialapi"
+      supervisord       :
+        command         : "#{GOBIN}/popularpost -c #{socialapi.configFilePath}"
+
+    notification        :
+      group             : "socialapi"
+      supervisord       :
+        command         : "#{GOBIN}/notification -c #{socialapi.configFilePath}"
+
+    trollmode           :
+      group             : "socialapi"
+      supervisord       :
+        command         : "#{GOBIN}/trollmode -c #{socialapi.configFilePath}"
+
+    pinnedpost          :
+      group             : "socialapi"
+      supervisord       :
+        command         : "#{GOBIN}/pinnedpost -c #{socialapi.configFilePath}"
+
+    algoliaconnector    :
+      group             : "socialapi"
+      supervisord       :
+        command         : "#{GOBIN}/algoliaconnector -c #{socialapi.configFilePath}"
+
+    activityemail       :
+      group             : "socialapi"
+      supervisord       :
+        command         : "#{GOBIN}/activityemail -c #{socialapi.configFilePath}"
+
+    dailyemail          :
+      group             : "socialapi"
+      supervisord       :
+        command         : "#{GOBIN}/dailyemail -c #{socialapi.configFilePath}"
+
+    privatemessageemailsender:
+      group             : "socialapi"
+      supervisord       :
+        command         : "#{GOBIN}/privatemessageemailsender -c #{socialapi.configFilePath}"
+
+    privatemessageemailfeeder:
+      group             : "socialapi"
+      supervisord       :
+        command         : "#{GOBIN}/privatemessageemailfeeder -c #{socialapi.configFilePath}"
+
+    sitemapfeeder       :
+      group             : "socialapi"
+      supervisord       :
+        command         : "#{GOBIN}/sitemapfeeder -c #{socialapi.configFilePath}"
+
+    sitemapgenerator    :
+      group             : "socialapi"
+      supervisord       :
+        command         : "#{GOBIN}/sitemapgenerator -c #{socialapi.configFilePath}"
+
+    collaboration       :
+      group             : "socialapi"
+      supervisord       :
+        command         : "#{GOBIN}/collaboration -kite-init -c #{socialapi.configFilePath}"
+
+    mailsender          :
+      group             : "socialapi"
+      supervisord       :
+        command         : "#{GOBIN}/emailsender -c #{socialapi.configFilePath}"
+
+    topicmoderation     :
+      group             : "socialapi"
+      supervisord       :
+        command         : "#{GOBIN}/topicmoderation -c #{socialapi.configFilePath}"
+
+    team                :
+      group             : "socialapi"
+      supervisord       :
+        command         : "#{GOBIN}/team -c #{socialapi.configFilePath}"
+
+    eventsender         :
+      group             : "socialapi"
+      supervisord       :
+        command         : "#{GOBIN}/eventsender -c #{socialapi.configFilePath}"

     gatekeeper          :
       group             : "socialapi"


dealing with disappearing edit boxes

dealing with disappearing edit boxes

addresses #8 in the header settings (form_name, description)

--- source/javascripts/src/view_xlformbuilder.coffee
+++ source/javascripts/src/view_xlformbuilder.coffee
@@ -105,15 +105,33 @@
     addOpts      = @$("#additional-options")

     # watch for changes on the title and description
-    for _s in ["form_title", "description"]
-      do (setting=_s)=>
-        elem = @$(".#{setting}")
-        elem.html es  if (es = @survey.settings.get(setting))
-        eip =
-          callback: (u, ent)=>
+    do =>
+      setting = "form_title"
+      elem = @$(".#{setting}")
+      if (es = @survey.settings.get(setting))
+        elem.html es
+      eip =
+        default_text: "New survey"
+        callback: (u, ent)=>
+          @survey.settings.set setting, ent
+          ent
+      elem.editInPlace eip
+    do =>
+      setting = "description"
+      elem = @$(".description")
+      default_text = "[survey description]"
+      if (es = @survey.settings.get(setting))
+        elem.html es
+      eip =
+        save_if_nothing_changed: true
+        default_text: default_text
+        callback: (u, ent)=>
+          if ent is ""
+            default_text
+          else
             @survey.settings.set setting, ent
             ent
-        elem.editInPlace eip
+      elem.editInPlace eip

     @survey.rows.trigger("reset")


activity: switch to Scroller api in PostPane

Conflicts:
	client/activity/lib/components/postpane/index.coffee

--- client/activity/lib/components/postpane/index.coffee
+++ client/activity/lib/components/postpane/index.coffee
@@ -26,10 +26,13 @@
     ActivityFlux.actions.message.createComment @message('id'), event.value

-  onScrollThresholdReached: (event) ->
+  onLoadMore: (event) ->
+
+    return  unless @props.messages.size
+    return  if @props.thread.getIn ['flags', 'isMessagesLoading']

     from = @props.messages.first().get('createdAt')
-    ActivityFlux.actions.message.loadComments @channel('id'), { from }
+    ActivityFlux.actions.message.loadComments @message('id'), { from }

   render: ->
@@ -38,7 +41,8 @@
       className="PostPane"
       messages={@props.messages}
       onSubmit={@bound 'onSubmit'}
-      onScrollThresholdReached={@bound 'onScrollThresholdReached'}
+      onLoadMore={@bound 'onLoadMore'}
+      isParticipant={@channel 'isParticipant'}
     />


默认返回并解析 json 内容

默认返回并解析 json 内容

--- src/httpclient.coffee
+++ src/httpclient.coffee
@@ -2,6 +2,10 @@
 http  = require 'http'
 querystring  = require 'querystring'
 URL  = require('url')
+jsons = JSON.stringify
+
+# 剥离出来的 HttpClient ，目前仅适合 qqapi 使用
+# 返回值：已经解析的json

 # 设置全局cookie
@@ -31,15 +35,25 @@
         resp.on 'data', (chunk) ->
             body += chunk
         resp.on 'end', ->
-            callback( body )
-    req.on "error" , (e)->
+            handle_resp_body(body, callback)
+    req.on "error" , (e)->
         callback(null,e)

     if params and options.method == 'POST'
-        # log data
         req.write(data);
     req.end();

+handle_resp_body = (body , callback) ->
+    err = null
+    try
+        ret = JSON.parse body
+    catch error
+        console.log "解析出错",body
+        console.log error
+        err = error
+        ret = null
+    callback(ret,err)
+

 http_get  = (options , callback) ->
     options.method = 'GET'


default-apps: Implemented openFileWithApplication method.

--- client/landing/app/MainApp/ApplicationManager.coffee
+++ client/landing/app/MainApp/ApplicationManager.coffee
@@ -129,6 +129,10 @@
             else do defaultCallback

       else do defaultCallback
+
+  openFileWithApplication: (appName, file) ->
+    @open appName, =>
+      @utils.defer => @getFrontApp().openFile file

   fetchManifests:(appName, callback)->

@@ -338,10 +342,6 @@

   # setGroup:-> console.log 'setGroup', arguments

-  # openFileWithApplication:(file, appPath)->
-  #   @open appPath, no, (app)->
-  #     app.openFile file
-
   # temp
   notification = null