improve link implement

improve link implement

--- app/scripts/editor.coffee
+++ app/scripts/editor.coffee
@@ -203,21 +203,25 @@
 					@saveSelection 'toolpad'
 					@showToolpadOverSelection 'toolpad'
 					@restoreSelection 'toolpad'
+		# apply link URL
 		$('#toolpad').delegate 'li:first-child', 'click', (e)->
 			self.openLinkInput()
-			if self.linkRange
-				a = document.createElement 'a'
-				a.setAttribute 'href', '#'
-				self.lastLinkElement = a
-				self.linkRange.surroundContents a
+			# if self.linkRange
+			# a = document.createElement 'a'
+			# a.setAttribute 'href', '#'
+			# self.lastLinkElement = a
+			# self.linkRange.surroundContents a
+			# document.execCommand('createlink', '#')
 		.delegate 'li:first-child', 'mouseenter', (e)->
 			self.linkRange = self.selection().getRangeAt 0
 		.delegate 'input', 'keydown', (e)->
-			if self.lastLinkElement
-				self.lastLinkElement.setAttribute 'href', $(@).val()
+				# self.lastLinkElement.setAttribute 'href', $(@).val()
 			if e.keyCode == 13
+				if self.linkRange
+					self.selection().removeAllRanges()
+					self.selection().addRange self.linkRange
+					document.execCommand('createlink', false, $(@).val())
 				self.hideToolpad()
-
 		$('#toolpad').delegate '.cancel', 'click', (e)=>
 			@cancelLinkInput()


autosavecallback

autosavecallback

--- app/scripts/editor.coffee
+++ app/scripts/editor.coffee
@@ -70,7 +70,7 @@
 			setTimeout =>
 				if @id && @status.changed
 					@setChanged false
-					@updateCallback() if @updateCallback
+					@autosaveCallback() if @autosaveCallback
 				update()
 			, durationInMilliseconds)()
 	divsToPs: ->


client: notify window resize listeners when collapsing the sidebar and when IDE is frontapp

--- client/Core/mainview.coffee
+++ client/Core/mainview.coffee
@@ -110,7 +110,18 @@
     @panelWrapper.addSubView new KDCustomHTMLView
       tagName  : 'cite'
       domId    : 'sidebar-toggle'
-      click    : => @toggleClass 'collapsed'
+      click    : @bound 'collapseSidebar'
+
+
+  collapseSidebar: ->
+
+    @toggleClass 'collapsed'
+
+    { appManager } = KD.singletons
+
+    if appManager.getFrontApp().getOption('name') is 'IDE'
+      KD.utils.wait 250, ->
+        KD.singletons.windowController.notifyWindowResizeListeners()

   createAccountArea:->


Moar defensive code

Moar defensive code

--- client/ide/lib/index.coffee
+++ client/ide/lib/index.coffee
@@ -982,9 +982,9 @@
   removeFakeViews: ->

     fakeEditorPane = @fakeEditor?.parent
-    fakeEditorPane?.parent.removePane fakeEditorPane
-
-    @fakeTerminalPane?.parent.removePane @fakeTerminalPane
+    fakeEditorPane?.parent?.removePane fakeEditorPane
+
+    @fakeTerminalPane?.parent?.removePane @fakeTerminalPane
     @fakeFinderView?.destroy()


enable panning in fullscreen view

enable panning in fullscreen view

--- app/assets/javascripts/states/monitoring/views/metric_fullscreen.js.coffee
+++ app/assets/javascripts/states/monitoring/views/metric_fullscreen.js.coffee
@@ -8,4 +8,6 @@
       "button.return": [ "mon_hosts_metric", -> { host: @host, check: @check, metric: @metric } ]

     after_render: ->
-      @graph = Bixby.monitoring.render_metric(@$("div.metric"), @metric)
+      if @graph = Bixby.monitoring.render_metric(@$("div.metric"), @metric)
+        @sync_helper = new Bixby.monitoring.PanSyncHelper(@graph)
+        @graph._bixby_touch_enabled = true # enable touch events in detailed view


fix nested_model_controls destroy confirmation

--- app/cells/folio/console/nested_model_controls/nested_model_controls.coffee
+++ app/cells/folio/console/nested_model_controls/nested_model_controls.coffee
@@ -20,8 +20,21 @@
       .val(i + 1)
       .trigger('change')

-$(document).on 'click', '.f-c-nested-model-controls__destroy-button', ->
+$(document).on 'click', '.f-c-nested-model-controls__destroy-button', (e) ->
   $button = $(this)
-  return $button.blur() unless window.confirm(window.FolioConsole.translations.removePrompt)
-  $button.closest('.nested-fields').prop('hidden', true)
-  $button.siblings('.f-c-nested-model-controls__destroy-input').val(1).trigger('change')
+
+  if window.confirm(window.FolioConsole.translations.removePrompt)
+    $nestedFields = $button.closest('.nested-fields')
+    $nestedFieldsParent = $nestedFields.parent()
+
+    if $button.data('remove')
+      $nestedFieldsParent.trigger('cocoon:before-remove', [$nestedFields, e])
+      $nestedFields.remove()
+      $nestedFieldsParent.trigger('cocoon:after-remove', [$nestedFields, e])
+    else
+      $nestedFields.prop('hidden', true)
+      $button.siblings('.f-c-nested-model-controls__destroy-input').val(1).trigger('change')
+  else
+    e.preventDefault()
+    e.stopPropagation()
+    $button.blur()


search magnet in existing tasks before trying to add a new

--- firefox/lib/urls.coffee
+++ firefox/lib/urls.coffee
@@ -1,3 +1,39 @@
+
+base32_decode = (s) ->
+	a = []
+	bits = 0
+	v = 0
+	s = s.toUpperCase()
+	for i in [0...s.length]
+		c = s.charCodeAt i
+		if 65 <= c <= 90
+			c -= 65
+		else if 50 <= c <= 55
+			c = c - 50 + 26
+		else
+			throw new Error("Incorrect base32 letter: #{s[i]}")
+		if bits < 3
+			bits += 5
+			v = (v << 5) | c
+		else if bits == 3
+			a.push ((v << 5) | c)
+			bits = 0
+			v = 0
+		else
+			a.push (v << (8 - bits)) | (c >> (bits - 3))
+			bits = bits - 3
+			v = c & ((1 << bits) - 1)
+	new Uint8Array a
+
+
+hex_char_code = (code) ->
+	('0' + code.toString(16)).slice(-2)
+
+array_to_hex = (a) ->
+	(hex_char_code(a[i]) for i in [0...a.length]).join ''
+
+base32_to_hex = (s) ->
+	array_to_hex base32_decode s

 is_ed2k = (url) ->
 	url.match /^ed2k:\/\//
@@ -34,13 +70,25 @@
 	url.match(ed2k_re)?[2].toLowerCase()

 magnet_to_infohash = (url) ->
-	throw new Error("Not Implemented")
+	url = url.replace /&.*$/, ''
+	info_hash = url.match(/^magnet:\?xt=urn:btih:(.+)/)?[1]
+	if not info_hash?
+		return
+	if info_hash.match /^[a-fA-F0-9]{40}$/
+		return info_hash.toLowerCase()
+	else if info_hash.match /^[A-Z2-7]{32}$/
+		return base32_to_hex info_hash
+	else
+		return

 normalize_url = (url) ->
 	url = url_unmask(url)
 	if url.match /magnet:/
-#		throw new Error("Not Implemented")
-		return url
+		hash = magnet_to_infohash url
+		if hash
+			return "bt://#{hash}"
+		else
+			return url
 	else if url.match /^ed2k:\/\//
 		hash = parse_ed2k_url_hash_hex url
 		if not hash


Clean up code for todolist component

Clean up code for todolist component

--- app/assets/javascripts/react_components/todo_list/app.js.coffee
+++ app/assets/javascripts/react_components/todo_list/app.js.coffee
@@ -16,12 +16,11 @@
                  constants.CLEAR_TODOS, @onClearTodos)

   onAddTodo: (payload) ->
-    id = @_nextTodoId()
+    id = @_nextTodoId
     todo =
       id:       id
       text:     payload.text
       complete: false
-
     @todos[id] = todo
     @emit 'change'

@@ -33,13 +32,14 @@
   onClearTodos: ->
     todos = @todos
     Object.keys(todos).forEach (key) ->
-      if todos[key].complete
-        delete todos[key]
+      delete todos[key] if todos[key].complete
     @emit 'change'

-  getState:    -> todos: @todos
+  getState: ->
+    todos: @todos

-  _nextTodoId: -> @todoId += 1
+  _nextTodoId: ->
+    @todoId += 1

 # Semantic actions

@@ -48,9 +48,13 @@
   toggleTodo: (id) -> @dispatch constants.TOGGLE_TODO, id:   id
   clearTodos:      -> @dispatch constants.CLEAR_TODOS

+# Instantiating our stores
+
+stores =
+  TodoStore: new TodoStore
+
 # Creation of a Flux instance

-stores = { TodoStore: new TodoStore }
 flux = new Fluxxor.Flux(stores, actions)

 # Logging upon the "dispatch" event
@@ -70,46 +74,54 @@
     newTodoText: ""

   getStateFromFlux: ->
-    flux = @getFlux()
-    flux.store('TodoStore').getState()
+    flux = @getFlux
+    flux.store('TodoStore').getState

   handleTodoTextChange: (e) ->
-    @setState { newTodoText: e.target.value }
+    @setState(newTodoText: e.target.value)

   onSubmitForm: (e) ->
-    e.preventDefault()
-    if @state.newTodoText.trim()
-      @getFlux().actions.addTodo @state.newTodoText
-      @setState { newTodoText: "" }
+    e.preventDefault
+    if @state.newTodoText.trim
+      @getFlux.actions.addTodo(@state.newTodoText)
+      @setState(newTodoText: "")

   clearCompletedTodos: (e) ->
-    @getFlux().actions.clearTodos()
+    @getFlux.actions.clearTodos

   render: ->
     todos = @state.todos

       <form onSubmit={@onSubmitForm}>

-          <input type="text" placeholder="New Todo"
+          <input type="text"
+                 placeholder="New Todo"
                  value={@state.newTodoText}
                  onChange={@handleTodoTextChange}
                  className="form-control" />

-          <input type="submit" value="Add Todo" className="btn btn-success"/>
-          <button onClick={@clearCompletedTodos} className="btn btn-default">Clear Completed</button>
+          <input type="submit"
+                 value="Add Todo"
+                 className="btn btn-success"/>
+          <button onClick={@clearCompletedTodos}
+                  className="btn btn-default">
+            Clear Completed
+          </button>

       </form>

         {
           Object.keys(todos).map (id) ->
-
<TodoItem todo={todos[id]} />
;
+

+              <TodoItem todo={todos[id]} />
+

         }

-# The React component (<TodoItem/>)
+# Defining a sub-component (<TodoItem/>)

 TodoItem = React.createClass
   mixins: [FluxMixin]
@@ -120,15 +132,18 @@
   # Its style will be determined based on the completion state.
   render: ->
     style =
-      textDecoration: if @props.todo.complete then "line-through" else ""
+      textDecoration:
+        if @props.todo.complete then "line-through" else ""

-    {@props.todo.text}
+
+      {@props.todo.text}
+

   # Clicking on a todo item will toggle the completion state.
   onClick: ->
-    @getFlux().actions.toggleTodo @props.todo.id
+    @getFlux.actions.toggleTodo(@props.todo.id)

-# Render the component.
+# Rendering the whole component
 document.addEventListener 'DOMContentLoaded', (e) ->
-  React.render <Application flux={flux} />,
-                document.getElementById 'react_mountPoint'
+  React.render <Application flux={flux}/>,
+                document.getElementById('react_mountPoint')


#96 Display error message if binary[color] undefined

--- app/assets/javascripts/oxalis/model.coffee
+++ app/assets/javascripts/oxalis/model.coffee
@@ -64,6 +64,9 @@
                   @binary[layer.typ] = new Binary(@user, tracing.content.dataSet, constants.TEXTURE_SIZE_P, supportedLayer)
                   zoomStepCount = Math.min(zoomStepCount, @binary[layer.typ].cube.ZOOM_STEP_COUNT - 1)

+            unless @binary["color"]?
+              Toast.error("No data available! Something seems to be wrong with the dataset.")
+
             # if "volume" layer still used, change name to segmentation
             if @binary["volume"]?
               @binary["segmentation"] = @binary["volume"]


Easy way to extract and set textStyle

Easy way to extract and set textStyle

--- framer/TextLayer.coffee
+++ framer/TextLayer.coffee
@@ -23,11 +23,13 @@
 		"font"
 	]

+	@_textStyleProperties = _.pull(_.clone(TextLayer._textProperties), "text").concat(["color", "shadowX", "shadowY", "shadowBlur", "shadowColor"])
+
 	explicitWidth: false

 	constructor: (options={}) ->

-		_.defaults options,
+		_.defaults options, options.textStyle,
 			backgroundColor: "transparent"
 			html: "Add text"
 			color: "#888"
@@ -132,6 +134,15 @@
 		else
 			layer.fontFamily = value

+	@define "textStyle",
+		get: ->
+			print TextLayer._textStyleProperties
+			_.pick @, TextLayer._textStyleProperties
+		set: (values) ->
+			for key, prop in _.pick values, TextLayer._textStyleProperties
+				@[key] = prop
+
+
 	@define "textDirection",
 		get: -> @direction
 		set: (value) -> @direction = value


:green_heart: Fix typo

:green_heart: Fix typo

--- lib/tree-view.coffee
+++ lib/tree-view.coffee
@@ -611,7 +611,7 @@
               newPath = "#{originalNewPath}#{fileCounter.toString()}"
             else
               extension = getFullExtension(originalNewPath)
-              filePath = path.join(path.dirname(originalNewPath), path.basename(originalNewPath, fullExtension))
+              filePath = path.join(path.dirname(originalNewPath), path.basename(originalNewPath, extension))
               newPath = "#{filePath}#{fileCounter.toString()}#{extension}"
             fileCounter += 1


Add mixin

Add mixin

--- lib/viewmodel.coffee
+++ lib/viewmodel.coffee
@@ -412,7 +412,7 @@

   load: (obj) ->
     viewmodel = this
-    for key, value of obj when key isnt 'share'
+    for key, value of obj when key isnt 'share' and key isnt 'mixin'
       if _.isFunction(value)
         # we don't care, just take the new function
         viewmodel[key] = value
@@ -481,6 +481,12 @@
       i++
     return

+  loadObj = (obj, collection, viewmodel) ->
+    if obj
+      array = if obj instanceof Array then obj else [obj]
+      for element in array
+        viewmodel.load collection[element]
+    return

   constructor: (initial) ->
     viewmodel = this
@@ -497,10 +503,9 @@
       i++ while parentPath[i] is viewmodelPath[i]
       difference = viewmodelPath.substr(i)
       return difference
-    if initial.share
-      shared = if initial.share instanceof Array then initial.share else [initial.share]
-      for share in shared
-        viewmodel.load ViewModel.shared[share]
+    loadObj initial.share, ViewModel.shared, viewmodel
+    loadObj initial.mixin, ViewModel.mixins, viewmodel
+
     return

@@ -559,4 +564,10 @@
         else
           ViewModel.shared[key][prop] = ViewModel.makeReactiveProperty(content)

+    return
+
+  @mixins = {}
+  @mixin = (obj) ->
+    for key, value of obj
+      ViewModel.mixins[key] = value
     return


tweak _handleClick

tweak _handleClick

--- client/navigator-location.coffee
+++ client/navigator-location.coffee
@@ -98,11 +98,14 @@
   # listener for click events. filters out clicks which we ignore handling
   # such as 'mailto:'.
   _handleClick: (event) ->
+
+    # return if not a simple click or it's already prevented.
     if @_which event isnt 1 or
       event?.metaKey? or event?.ctrlKey? or event?.shiftKey? or
       event.defaultPrevented
         return

+    # get anchor element above the clicked element
     el = event.target # TODO: better way to find parent anchor element?
     until not el? or el?.nodeName is 'A' then el = el?.parentNode
     unless el?.nodeName is 'A' then return
@@ -120,7 +123,7 @@
     if el?.target then return

     if el?.origin? and el.origin isnt @_origin() then return
-    else if el?.href?.indexOf(@_origin()) isnt 0 then return
+    if el?.href?.indexOf(@_origin()) isnt 0 then return

     path = @_elementPath el


pre-release cleanup for #190

pre-release cleanup for #190

--- chrome/content/zotero-better-bibtex/zotero-better-bibtex.coffee
+++ chrome/content/zotero-better-bibtex/zotero-better-bibtex.coffee
@@ -540,10 +540,8 @@
       itemID = (if typeof item == 'number' then item else parseInt(item))
       serialized = @serialized[itemID]
       if serialized?.itemType == 'attachment'
-        Zotero.BetterBibTeX.log("serialization cache hit for #{item}")
         return JSON.parse(JSON.stringify(serialized))
       else
-        Zotero.BetterBibTeX.log("serialization cache miss for #{item}")
         return null

     # no serialization for attachments when their data is exported
@@ -553,10 +551,7 @@

     itemID = (if typeof item.itemID == 'number' then item.itemID else parseInt(item.itemID))
     serialized = @serialized[itemID]
-    if serialized
-      Zotero.BetterBibTeX.log("serialization cache hit for #{item.itemID}")
-    else
-      Zotero.BetterBibTeX.log("serialization cache miss for #{item.itemID}")
+    if !serialized
       serialized = (if item.isAttachment() then @_attachmentToArray(item) else @_itemToArray(item))
       if serialized
         @serialized[itemID] = serialized


added support for files starting with a shebang

--- lib/coffee-autocompile-view.coffee
+++ lib/coffee-autocompile-view.coffee
@@ -60,7 +60,7 @@
           return

         key = item.substr(0, i).trim()
-        match = /^\s*\/\/\s*(.+)/.exec(key);
+        match = /^\s*#\s*(.+)/.exec(key)

         if match
           key = match[1]
@@ -91,7 +91,7 @@
     firstLine = null

     rl.on 'line', (line) ->
-      if firstLine is null
+      if firstLine is null and not line.match(/^#!/)?
         firstLine = line
         parse firstLine


ViewportSystem now leaps directly to new areas if target is not within the buffer zone of the previous area

--- src/javascript/game/systems/viewport_system.coffee
+++ src/javascript/game/systems/viewport_system.coffee
@@ -3,6 +3,34 @@
 MathUtils = require '../../utils/math_utils'
 Common = require '../entity/components'
 MapConfig = require '../map/config'
+
+trackTarget = (targetPosition,viewportPosition,viewportArea,config) ->
+  viewportX = MathUtils.clamp(
+    MathUtils.keepWithin(
+      viewportPosition.get('x')
+      targetPosition.get('x')
+      config.get('trackBufLeft')
+      config.get('trackBufRight'))
+    viewportArea.bounds.left
+    viewportArea.bounds.right - config.get('width') # TODO area.bounds.right
+  )
+
+  viewportY = MathUtils.clamp(
+    MathUtils.keepWithin(
+      viewportPosition.get('y')
+      targetPosition.get('y')
+      config.get('trackBufTop')
+      config.get('trackBufBottom'))
+    viewportArea.bounds.top # TODO area.bounds
+    viewportArea.bounds.bottom - config.get('height') # TODO area.bounds
+  )
+  return viewportPosition.set('x',viewportX).set('y',viewportY)
+
+shuttleZoneBuffer=32
+withinShuttlingDistance = (targetPosition, viewportPosition) ->
+  tpx = targetPosition.get('x')
+  vpx = viewportPosition.get('x')
+  return (tpx - (vpx + MapConfig.roomWidthInPixels) <= shuttleZoneBuffer) and (tpx - vpx >= -shuttleZoneBuffer)

 class ViewportSystem extends BaseSystem
   @Subscribe: [
@@ -16,48 +44,24 @@
     worldMap = @input.getIn(['static','worldMap'])
     viewport = @getComp('viewport')

-    if @_transitionToNewArea(worldMap, viewportPosition, targetPosition)
-      return
+    config = viewport.get('config')

     viewportArea = worldMap.getAreaAt(viewportPosition.get('x'), viewportPosition.get('y'))
-    config = viewport.get('config')
+    targetArea = worldMap.getAreaAt(targetPosition.get('x'), targetPosition.get('y'))

-    viewportX = MathUtils.clamp(
-      MathUtils.keepWithin(
-        viewportPosition.get('x')
-        targetPosition.get('x')
-        config.get('trackBufLeft')
-        config.get('trackBufRight'))
-      viewportArea.bounds.left
-      viewportArea.bounds.right - config.get('width') # TODO area.bounds.right
-    )
+    if viewportArea.name != targetArea.name
+      if withinShuttlingDistance(targetPosition,viewportPosition)
+        @_shuttleToNewArea(worldMap, targetPosition,targetArea, viewportPosition)
+        return
+      else
+        # This will cause trackTarget to directly calculate the proper new viewport
+        viewportArea = targetArea
+
+    newViewportPos = trackTarget(targetPosition, viewportPosition, viewportArea, config)
+    @updateComp newViewportPos
+

-    viewportY = MathUtils.clamp(
-      MathUtils.keepWithin(
-        viewportPosition.get('y')
-        targetPosition.get('y')
-        config.get('trackBufTop')
-        config.get('trackBufBottom'))
-      viewportArea.bounds.top # TODO area.bounds
-      viewportArea.bounds.bottom - config.get('height') # TODO area.bounds
-    )
-
-    @updateComp viewportPosition.set('x',viewportX).set('y',viewportY)
-
-  _transitionToNewArea: (worldMap, viewportPosition, targetPosition) ->
-    viewportArea = worldMap.getAreaAt(viewportPosition.get('x'), viewportPosition.get('y'))
-    targetArea = worldMap.getAreaAt(targetPosition.get('x'), targetPosition.get('y'))
-
-    if targetArea and viewportArea and targetArea.name != viewportArea.name
-      # We need to transition to new area
-    else
-      return false
-
-    # ViewportShuttle: alternate means of adjusting the viewport.
-    # Remove the "normal" gameplay viewport tracking behavior (loose-follow of Samus)
-    # and temporarliy lock the viewport onto a traveler that smoothly slides from
-    # one "area" to the next.  ...and then start following Samus again.
-
+  _shuttleToNewArea: (worldMap, targetPosition, targetArea, viewportPosition ) ->
     # What room are we shuttling to?
     nextRoom = worldMap.getRoomAt(targetPosition.get('x'), targetPosition.get('y'))
     return false if nextRoom.nil?
@@ -85,8 +89,6 @@
         x: nextRoom.col * MapConfig.roomWidthInPixels
         y: nextRoom.row * MapConfig.roomHeightInPixels
     ]
-    # Don't forget to return "true"
-    return true

 module.exports = ViewportSystem


Removed comment

Removed comment

--- t9n/fr.coffee
+++ t9n/fr.coffee
@@ -1,4 +1,3 @@
-# We need a dummy translation so that a text is found.
 fr =
   error:
     accounts:


Update it.coffee

Update it.coffee

Removed localized text with parameters.

--- t9n/it.coffee
+++ t9n/it.coffee
@@ -45,7 +45,7 @@

   "A new email has been sent to you. If the email doesn't show up in your inbox, be sure to check your spam folder.": "Ti è stata inviata una nuova email. Se non trovi l' email nella tua posta in arrivo controllate che non sia stata spostata nella cartella SPAM."
   "Already verified": "Gi\à verificato"
-  "At least 1 digit, 1 lowercase and 1 uppercase": "Almeno @{digit} numero, @{lowercase} carattere minuscolo e @{uppercase} maiuscolo"
+  "At least 1 digit, 1 lowercase and 1 uppercase": "Almeno 1 numero, 1 carattere minuscolo e 1 maiuscolo"
   "Maximum allowed length": "Lunghezza massima consentita"
   "Minimum required length": "Lunghezza minima consentita"
   "Please verify your email first. Check the email and follow the link!": "Per favore, verifica prima la tua email. Controlla la tua email e segui il collegamento che ti è stato inviato."
@@ -118,6 +118,4 @@
       #---- Misc...
       "Unknown error": "Errore Sconosciuto"

-T9n.get 'At least 1 digit, 1 lowercase and 1 uppercase', true, {digit: 'un', lowercase: 1, uppercase: 1}
-
 T9n.map "it", it


Add placeholder back to comment box

Add placeholder back to comment box

--- app/talk/comment-box.cjsx
+++ app/talk/comment-box.cjsx
@@ -88,7 +88,7 @@
             <CommentPreview header={null} content={@state.reply}/>
           </div>}

-        <MarkdownEditor className="full" value={@state.content} onChange={@onInputChange} helpText={<CommentHelp />}/>
+        <MarkdownEditor placeholder={@props.placeholder} className="full" value={@state.content} onChange={@onInputChange} helpText={<CommentHelp />}/>

         <section>
           <button


small refactoring on file system module

small refactoring on file system module

--- backend/filesystem.coffee
+++ backend/filesystem.coffee
@@ -15,32 +15,35 @@
 async = require 'async'
 events = require 'events'

+
 remoteConfig = config.getConfig()

-module.exports =
+changeHandler =  (task, callback) =>
+    deviceName = config.getDeviceName()
+
+    switch task.operation
+        when 'put'
+            if task.file?
+                filesystem.createFileDoc task.file, callback
+        when 'get'
+            if task.file?
+                binary.fetchOne deviceName, task.file, callback
+            else
+                binary.fetchAll deviceName, callback
+        else
+            # rebuild
+            if task.file?
+                filesystem.buildTree task.file, callback
+            else
+                filesystem.buildTree null, callback
+
+
+filesystem =

     # Changes is the queue of operations, it contains
     # files that are being downloaded, and files to upload.
-    changes: async.queue (task, callback) ->
-        deviceName = config.getDeviceName()
-
-        switch task.operation
-            when 'put'
-                if task.file?
-                    @createFileDoc task.file, callback
-            when 'get'
-                if task.file?
-                    binary.fetchOne deviceName, task.file, callback
-                else
-                    binary.fetchAll deviceName, callback
-            else
-                # rebuild
-                if task.file?
-                    @buildTree task.file, callback
-                else
-                    @buildTree null, callback
-    , 1
+    changes: async.queue changeHandler, 1

     watchingLocked: false

@@ -446,3 +449,6 @@
         paths = @getPaths filePath
         return paths.relative isnt '' \
            and paths.relative.substring(0,2) isnt '..'
+
+
+module.exports = filesystem


Pass 1 for windows

Pass 1 for windows

--- lib/views/synced-fs.coffee
+++ lib/views/synced-fs.coffee
@@ -1,6 +1,7 @@
 {$, View} = require 'atom-space-pen-views'
 file_sys  = require 'fs'
 mkdirp    = require 'mkdirp'
+exec      = require('child_process').execSync

 module.exports =
 class TerminalView extends View
@@ -29,7 +30,13 @@
       files = file_sys.readdirSync(path)
       files.forEach (file, index) ->
         curPath = path + sep + file
-        if file_sys.lstatSync(curPath).isDirectory()
+
+        if sep == '\\'
+          isdir = exec('if exist ' + curPath + '\* echo true').match(/true/)
+        else
+          isdir = file_sys.lstatSync(curPath).isDirectory()
+
+        if isdir
           self.deleteDirectoryRecursive(curPath)
         else
           file_sys.unlinkSync(curPath)


Reliable pattern to ignore Firebase local changes.

The Firebase js client design is really wrong designed. This is temp
workaround, and the only one good solution is not use Firebase in
browser. TODO: Move Firebase logic to server.

--- app/client/js/firebase.coffee
+++ app/client/js/firebase.coffee
@@ -15,6 +15,12 @@
     @type {Firebase}
   ###
   userRef: null
+
+  ###*
+    @type {boolean}
+    @private
+  ###
+  isLocalChange_: false

   ###*
     @protected
@@ -63,28 +69,21 @@
   onUserLogin: (user) ->
     @userRef = @userRefOf user
     userJustLogged = true
-    # The problem with Firebase on 'value' is, that this method is dispatched
-    # two times when Firebase.ServerValue.TIMESTAMP is used.
-    # 1. locally made change
-    # 2. server with updated TIMESTAMP value.
     @userRef.on 'value',
       (snap) =>
+        # Ignore local changes, workaround for Firebase behavior.
+        return if @isLocalChange_
         # Can be null for new users.
         val = snap.val()
-        # This check is used to ignore local Firebase changes.
-        # TODO: Consider splitting to localUpdated and serverUpdated.
-        return if val && val.updated == @userStore.updated
-        # Merge server changes to local.
+        # Merge server changes to client.
         @userStore.updateFromServer user, val
         if userJustLogged
           userJustLogged = false
-          # Notify will rerun sync to server, so local changes made before login
-          # will be saved.
+          # Sync with saving to server to preserve local changes.
           @userStore.notify()
         else
-          # User is already loged, to we need only to save server changes.
+          # Only local sync.
           @userStore.serverNotify()
-
     , (error) ->
       # TODO: Report to server.
       if goog.DEBUG
@@ -101,8 +100,16 @@
       .child user.uid

   onUserLogout: ->
-    @userRef?.off 'value'
-    @userStore.onLogout !!@userRef
+    userWasLogged = !!@userRef
+    @userRef.off 'value' if userWasLogged
+    @userRef = null
+    ###*
+      PATTERN: Logout has to delete all data in memory and in localStorage, but
+      only if user was logged before. We don't want to delete data for not yet
+      registered/logged user.
+    ###
+    @userStore.clearOnLogout userWasLogged
+    @userStore.serverNotify()

   loginViaFacebook: ->
     @authClient.login 'facebook',
@@ -112,3 +119,21 @@

   logout: ->
     @authClient.logout()
+
+  ###*
+    Use this method for any Firebase state changing method. It temporally
+    disables local change propagation. Firebase by default calls listeners
+    immediatelly after any set/update. This hack allows us to bypass this IMHO
+    wrong behavior.
+    As result, Firebase.ServerValue.TIMESTAMP isn't immediatelly propagated to
+    local state. If you need immediate propagation, use your own local time
+    implementation in store.
+    @param {function(this:app.Firebase)} callback
+  ###
+  sync: (callback) ->
+    @isLocalChange_ = true
+    try
+      # Callback immediately invokes @userRef.on handler.
+      callback.call @
+    finally
+      @isLocalChange_ = false


fix typo in log message

fix typo in log message

--- app/coffee/TikzManager.coffee
+++ app/coffee/TikzManager.coffee
@@ -30,6 +30,6 @@
 			return callback(error) if error?
 			fs.readFile path, "utf8", (error, content) ->
 				return callback(error) if error?
-				logger.log compileDir: compileDir, mainFile: mainFile, "copied file to ouput.tex for tikz"
+				logger.log compileDir: compileDir, mainFile: mainFile, "copied file to output.tex for tikz"
 				# use wx flag to ensure that output file does not already exist
 				fs.writeFile Path.join(compileDir, "output.tex"), content, {flag:'wx'}, callback


fix hide projects menu

fix hide projects menu

--- app/coffee/modules/nav.coffee
+++ app/coffee/modules/nav.coffee
@@ -160,6 +160,7 @@

         $scope.$on "$viewContentLoaded", (ctx) ->
             if ctx.targetScope.$$childHead is null || ctx.targetScope.$$childHead.hideMenu
+                $el.addClass("hidden")
                 $log.error "No scope found for render menu."
                 return


Fixng Jit.si url videoconference generation

--- app/coffee/modules/nav.coffee
+++ app/coffee/modules/nav.coffee
@@ -249,21 +249,24 @@
         container.replaceWith(dom)

     videoConferenceUrl = (project) ->
-        urlSeparator = "-"
+        urlFixer = (url) -> return url
+
         if project.videoconferences == "appear-in"
             baseUrl = "https://appear.in/"
         else if project.videoconferences == "talky"
             baseUrl = "https://talky.io/"
         else if project.videoconferences == "jitsi"
-            urlSeparator = ""
             baseUrl = "https://meet.jit.si/"
+            urlFixer = (url) -> return url.replace(/ /g, "").replace(/-/g, "")
         else
             return ""

         if project.videoconferences_salt
-            url = "#{project.slug}#{urlSeparator}#{project.videoconferences_salt}"
+            url = "#{project.slug}-#{project.videoconferences_salt}"
         else
             url = "#{project.slug}"
+
+        url = urlFixer(url)

         return baseUrl + url


add ageBracket for leaderboard-my-row (#6546)

Co-authored-by: smallst_wsl <6c42689ff77371401bf43b7d56b93ac1ca3e92d3@163.com>

--- app/views/ladder/LadderTabView.coco.coffee
+++ app/views/ladder/LadderTabView.coco.coffee
@@ -439,6 +439,9 @@
     above = @playersAbove.models
     l = l.concat(above)
     l.reverse()
+    if @session?.get('creatorAge') and !(@session?.get('ageBracket'))
+      # add ageBracket for @session
+      @session.set('ageBracket', utils.ageToBracket(@session.get('creatorAge')))
     l.push @session
     l = l.concat(@playersBelow.models)
     if @myRank is 'unknown'


Pass on client-side routes

Pass on client-side routes

--- apps/artwork/index.coffee
+++ apps/artwork/index.coffee
@@ -10,3 +10,8 @@
 app.set 'view engine', 'jade'

 app.get '/artwork/:id', routes.index
+
+# Handled client-side
+app.get '/artwork/:id/zoom', routes.index
+app.get '/artwork/:id/more-info', routes.index
+app.get '/artwork/:id/view-in-room', routes.index


Remove console.log lines

Remove console.log lines

--- scripts/delete.coffee
+++ scripts/delete.coffee
@@ -2,14 +2,11 @@
 Q = require('q')

 loadRecentMessages = (http, token, channel, robotId, count) ->
-  console.log "loadRecentMessages"
   count = count || 50
   url = "https://slack.com/api/channels.history?" +
     "token=#{token}&channel=#{channel}&count=#{count}"
   deferred = Q.defer()
   http(url).get() (err, res, body) ->
-    console.log "Received response from #{url}"
-    console.log "err =", err
     if err
       deferred.reject "I don't know what I've said lately: #{err}"
     else
@@ -19,19 +16,14 @@
         .sortBy('ts')
         .reverse()
         .value()
-      console.log 'myMessages =', myMessages
-      console.log 'deferred =', deferred
       deferred.resolve(myMessages)
   return deferred.promise

 deleteMessage = (http, token, channel, message) ->
-  console.log "deleteMessage"
   url = "https://slack.com/api/chat.delete?" +
     "token=#{token}&channel=#{channel}&ts=#{message.ts}"
   deferred = Q.defer()
   http(url).get() (err, res, body) ->
-    console.log "Received response from #{url}"
-    console.log "err =", err
     if err
       deferred.reject "I couldn't delete my message: #{err}"
     else
@@ -39,7 +31,6 @@
   return deferred.promise

 selectMessage = (messages, age) ->
-  console.log "selectMessage"
   deferred = Q.defer()
   if age < messages.length
     deferred.resolve(messages[age])


sake, lunch, oayatsuを追加

sake, lunch, oayatsuを追加

--- scripts/harahe.coffee
+++ scripts/harahe.coffee
@@ -6,6 +6,11 @@
 #
 # Commands:
 #   hubot harahe - Reply with restaurant info
+#   hubot osake  - Reply with restaurant info
+#   hubot sake   - Reply with restaurant info
+#   hubot lunch  - Reply with restaurant info
+#   hubot oyatsu - Reply with restaurant info
+#   hubot oyatu  - Reply with restaurant info

 Client = require("node-rest-client").Client
 client = new Client()
@@ -14,7 +19,7 @@
 keyid       = process.env.HUBOT_HARAHE_TOKEN
 apiHost     = 'http://api.gnavi.co.jp/RestSearchAPI/20150630/?'

-sendRestaurant = (msg, query, budgetKey = "budget") ->
+sendRestaurant = (msg, query, prefix = '', budgetKey = 'budget') ->
   formatBudget = (budget) -> if typeof budget == 'string' then "#{budget}円" else "不明"
   request = apiHost + stringify(query)

@@ -27,7 +32,7 @@
     page = Math.floor Math.random() * Math.min(response['total_hit_count'], response['hit_per_page'])
     restaurant = if response['total_hit_count'] == '1' then response['rest'] else response['rest'][page]
     msg.send """
-    #{restaurant['name']}
+    #{prefix}#{restaurant['name']}

     カテゴリ: #{restaurant['category']}
     平均予算: #{formatBudget(restaurant[budgetKey])}
@@ -44,3 +49,33 @@
       format: 'json'
     }
     sendRestaurant(msg, query)
+
+  robot.respond /O?SAKE ?(.*)$/i, (msg) ->
+    query = {
+      keyid: keyid
+      hit_per_page: 20
+      address: msg.match[1].trim()
+      freeword: '酒'
+      format: 'json'
+    }
+    sendRestaurant(msg, query, ':sake:')
+
+  robot.respond /LUNCH ?(.*)$/i, (msg) ->
+    query = {
+      keyid: keyid
+      hit_per_page: 20
+      address: msg.match[1].trim()
+      lunch: 1
+      format: 'json'
+    }
+    sendRestaurant(msg, query, '', 'lunch')
+
+  robot.respond /OYATS?U ?(.*)$/i, (msg) ->
+    query = {
+      keyid: keyid
+      hit_per_page: 20
+      address: msg.match[1].trim()
+      freeword: 'カフェ・スイーツ'
+      format: 'json'
+    }
+    sendRestaurant(msg, query, ':cake:')


Moved method calls to separate lines

Moved method calls to separate lines

--- app/assets/javascripts/awards_handler.coffee
+++ app/assets/javascripts/awards_handler.coffee
@@ -114,12 +114,17 @@
     emojiCssClass = @resolveNameToCssClass(emoji)

     nodes = []
-    nodes.push "<button class='btn award-control js-emoji-btn has_tooltip active' title='me'>"
-    nodes.push "

"
-    nodes.push "1"
-    nodes.push "</button>"
-
-    emoji_node = $(nodes.join("\n")).insertBefore(".js-award-holder").find(".emoji-icon").data("emoji", emoji)
+    nodes.push(
+      "<button class='btn award-control js-emoji-btn has_tooltip active' title='me'>",
+      "

",
+      "1",
+      "</button>"
+    )
+
+    emoji_node = $(nodes.join("\n"))
+      .insertBefore(".js-award-holder")
+      .find(".emoji-icon")
+      .data("emoji", emoji)
     $('.award-control').tooltip()

   resolveNameToCssClass: (emoji) ->


Scroll to first unread comment on discussion page load

--- app/assets/javascripts/discussions.js.coffee
+++ app/assets/javascripts/discussions.js.coffee
@@ -7,6 +7,11 @@

 $ ->
   if $("body.discussions.show").length > 0
+
+    if $('#js-dog-ear').length > 0
+      $('html,body').animate
+        scrollTop: $('#js-dog-ear').offset().top - 75
+
     autocomplete_path = $('#comment-input').data('autocomplete-path')
     $("textarea").atwho
       at: '@'


Split checks.

Split checks.

--- scripts/points.coffee
+++ scripts/points.coffee
@@ -122,7 +122,7 @@
     #
     #         msg.send "The top #{pointcount} users with the most points are: #{topscore}"

-    robot.respond /output$/i, (msg) ->
+    robot.respond /output1$/i, (msg) ->
         if robot.auth.hasRole(msg.envelope.user, ['admin', 'moderator'])
             # Returns active users only with their points
             # score = ["#{username} has #{points[username]}" for username in people]
@@ -131,12 +131,34 @@
             # score = ["#{username} has #{points[username]}" for points in robot.brain.data.points]

             points[username] ?= 0
+            output1 = ["#{username} has #{points[username]}" for points in points]
+            outputs1 = ["Output 1: #{output1}"]

-            output1 = ["#{username} has #{points[username]}" for points in points]
+            msg.send "Brain output tests: #{outputs1}"
+
+    robot.respond /output2$/i, (msg) ->
+        if robot.auth.hasRole(msg.envelope.user, ['admin', 'moderator'])
+
+            points[username] ?= 0
             output2 = ["#{username} has #{points[username]}" for points in robot.brain.data.points]
+            outputs2 = ["Output 2: #{output2}"]
+
+            msg.send "Brain output tests: #{outputs2}"
+
+    robot.respond /output3$/i, (msg) ->
+        if robot.auth.hasRole(msg.envelope.user, ['admin', 'moderator'])
+
+            points[username] ?= 0
             output3 = ["#{username} has #{points[username]}" for points in robot.brain.data.points.username]
+            outputs3 = ["Output 3: #{output3}"]
+
+            msg.send "Brain output tests: #{outputs3}"
+
+    robot.respond /output4$/i, (msg) ->
+        if robot.auth.hasRole(msg.envelope.user, ['admin', 'moderator'])
+
+            points[username] ?= 0
             output4 = ["#{username} has #{points[username]}" for points in robot.brain.data('points')]
+            outputs4 = ["Output 4: #{output4}"]

-            outputs = ["Output 1: #{output1}, Output 2: #{output2}, Output 3: #{output3}, Output 4: #{output4}"]
-
-            msg.send "Brain output tests: #{outputs}"
+            msg.send "Brain output tests: #{outputs4}"


Fix for photo grid overlap

Fix for photo grid overlap

--- app/assets/javascripts/photographs.js.coffee
+++ app/assets/javascripts/photographs.js.coffee
@@ -5,7 +5,8 @@
     opts = wookmarkOptions(calculateGridWidth())
     photoGrid.find(".photo, .user-block").wookmark(opts)

-  photoGrid.trigger "reload:grid"
+  photoGrid.imagesLoaded ->
+    photoGrid.trigger "reload:grid"

   $(window).resize ->
     photoGrid.trigger "reload:grid"


oops fixed syntax error

oops fixed syntax error

--- src/viz/types/helpers/graph/includes/buffer.coffee
+++ src/viz/types/helpers/graph/includes/buffer.coffee
@@ -69,7 +69,7 @@
               domain[0] -= 1
               domain[1] += 1

-        else if vars[axis].value if vars.time.value
+        else if vars[axis].value is vars.time.value
           difference = Math.abs domain[1] - domain[0]
           additional = difference / (vars[axis].ticks.values.length - 1)
           additional = additional / 2
@@ -78,7 +78,6 @@
           domain[1] = domain[1] + additional

         else
-
           difference = Math.abs domain[1] - domain[0]
           add = difference / 2


changes all instances of 'beta' to 'review' in text

--- app/pages/lab/visibility.cjsx
+++ app/pages/lab/visibility.cjsx
@@ -63,18 +63,18 @@

           <button type="button" className="standard-button" disabled={@props.project.private or not @props.project.live or @state.setting.beta_requested or @props.project.beta_requested} onClick={@set.bind this, 'beta_requested', true}>Apply for review</button>{' '}
           {if @props.project.private
-            Only public projects can apply for a beta.
+            Only public projects can apply for review.
           else if not @props.project.live
-            Only live projects can apply for a beta.
+            Only live projects can apply for review.
           else if @props.project.beta_requested
-            Beta status has been applied for. <button type="button" disabled={@state.setting.beta_requested} onClick={@set.bind this, 'beta_requested', false}>Cancel application</button>}
+            Review status has been applied for. <button type="button" disabled={@state.setting.beta_requested} onClick={@set.bind this, 'beta_requested', false}>Cancel application</button>}

Pending approval, expose this project to users who have opted in to help test new projects.

         {if @props.project.beta_approved

-            Beta status for this project has been approved. To end the beta test and make changes, switch back to development mode.
+            Review status for this project has been approved. To end the review and make changes, switch back to development mode.
             {unless @props.project.launch_requested or @props.project.launch_approved
               If you’re ready to launch this project, see the next section.}

}
@@ -86,7 +86,7 @@
             <button type="button" className="standard-button" disabled={not @props.project.beta_approved or @state.setting.launch_requested or @props.project.launch_requested} onClick={@set.bind this, 'launch_requested', true}>Apply for full launch</button>{' '}

             {unless @props.project.beta_approved
-              Only projects in beta can apply for a full launch.}
+              Only projects in review can apply for a full launch.}
             {if @props.project.launch_approved
               This project is available to the whole Zooniverse!
             else if @props.project.launch_requested


Fix service_node URL handling and redirect treenode to service_node

Fixes two problems.

1. All unit?service_node=N URLs were broken
2. All leagacy unit?treenode=N URLs were not redirected
   to the correct URL

--- server-src/dev.coffee
+++ server-src/dev.coffee
@@ -69,6 +69,9 @@
         if not match
             next()
             return
+        if req.query.treenode?
+            fullUrl = req.originalUrl.replace(/treenode/g, 'service_node');
+            res.redirect 301, fullUrl
         host = req.hostname
         config.default_language = get_language host
         config.is_embedded = options.embedded
@@ -102,7 +105,7 @@
     next()

 handleUnit = (req, res, next) ->
-    if req.query.service? or req.query.division? or req.query.treenode?
+    if req.query.service? or req.query.division? or req.query.service_node? or req.query.treenode?
         requestHandler req, res, next
         return
     pattern = /^\/(\d+)\/?$/


Fixed display of cartesian rings on the flat map.

--- viz/flatland/geom/cartesian_ring.coffee
+++ viz/flatland/geom/cartesian_ring.coffee
@@ -37,9 +37,10 @@
       displayedPoints = _.map(@points, (p)-> p.displayedPoint)
       @lines = []
       _.eachCons(displayedPoints, 2, (pair)=>
-        @lines.push(board.create('line', displayedPoints, CartesianRing.LINE_STYLE)))
+        @lines.push(board.create('line', pair, CartesianRing.LINE_STYLE)))
       if @closed
-        @lines.push(board.create("line", [displayedPoints[0], displayedPoints[displayedPoints.length - 1]]))
+        pair = [displayedPoints[0], displayedPoints[displayedPoints.length - 1]]
+        @lines.push(board.create("line", pair, CartesianRing.LINE_STYLE))

   undisplay: (board)->
     p.undisplay(board) for p in @points


PollModel: fix for sentry "this.results is undefined"

--- vue/src/shared/models/poll_model.coffee
+++ vue/src/shared/models/poll_model.coffee
@@ -42,6 +42,7 @@
     attachments: []
     linkPreviews: []
     notifyOnClosingSoon: 'undecided_voters'
+    results: []
     pleaseShowResults: false
     recipientMessage: null
     recipientAudience: null


add support for passing in constructed content

--- modules/notify/main/index.coffee
+++ modules/notify/main/index.coffee
@@ -5,7 +5,16 @@
       .add(hx.detached('i').class('hx-notification-icon ' + notification.options.icon))

   content = hx.detached('div').class('hx-notification-content')
-  notification.options.renderer(content.node(), notification.message)
+  msg = notification.message
+
+  msgIsNode = (msg instanceof hx.Selection) or (msg instanceof HTMLElement)
+  msgIsArrayOfNodes = msgIsNode or hx.isArray(msg) and msg.every (item) ->
+    (item instanceof hx.Selection) or (item instanceof HTMLElement)
+
+  if msgIsNode or msgIsArrayOfNodes
+    content.add(msg)
+  else
+    notification.options.renderer(content.node(), msg)

   if notification.options.pinnable
     pin = hx.detached('div')
@@ -85,6 +94,7 @@
 updatePinnedStatus = (notification) ->
   notification.domPin.classed('hx-notification-pin-pinned', notification.pinned)

+defaultRenderer = (node, message) -> hx.select(node).text(message)

 class Notification
   constructor: (@manager, @message, options) ->
@@ -93,8 +103,11 @@
       cssclass: undefined
       timeout: @manager._.defaultTimeout
       pinnable: true
-      renderer: (node, message) -> hx.select(node).text(message)
+      renderer: defaultRenderer
     }, options
+
+    # Prevent undefined renderers being passed in
+    @options.renderer ?= defaultRenderer

     @id = nextId(@manager)


Fix CreateAccountModal tests

Fix CreateAccountModal tests

--- test/app/views/core/CreateAccountModal.spec.coffee
+++ test/app/views/core/CreateAccountModal.spec.coffee
@@ -16,7 +16,7 @@
 }

-xdescribe 'CreateAccountModal', ->
+describe 'CreateAccountModal', ->

   modal = null

@@ -601,6 +601,7 @@
       spyOn(api.users, 'signupWithFacebook').and.returnValue(Promise.resolve())
       spyOn(api.users, 'signupWithPassword').and.returnValue(Promise.resolve())
       spyOn(api.trialRequests, 'post').and.returnValue(Promise.resolve())
+      spyOn(application.tracker, 'updateTrialRequestData').and.returnValue(Promise.resolve())
       @dispatch = jasmine.createSpy()
       @commit = jasmine.createSpy()
       @rootState = {


fixed spec

fixed spec

--- test/unit/directives/phrase_javascript_spec.coffee
+++ test/unit/directives/phrase_javascript_spec.coffee
@@ -54,7 +54,7 @@
         scope.$digest()

     it "should attach the project id as a global variable", ->
-      expect(window.PHRASEAPP_CONFIG = {phrase_project_id}).toEqual("my-project-id")
+      expect(window.PHRASEAPP_CONFIG.projectId).toEqual("my-project-id")

     it "should fetch and evaluate the javascript snippet", ->
       expect(window.jQuery.getScript).toHaveBeenCalled()


Up to date content

Up to date content

--- app/translations/en_us.coffee
+++ app/translations/en_us.coffee
@@ -12,6 +12,7 @@
     subheading: "Massive galaxies warp space-time around themselves, bending light rays so that we can see around them. They're the Universe's own telescopes, but these gravitational lenses are very rare: we need your help to find them!"
     discover: "Discover Lenses"
     discover_snippet: "Help scientists identify these very rare astronomical objects."
+    refine_snippet: "Help scientists refine a sample of Space Warps lens candidates."
     learn: "Learn about Lenses"
     learn_snippet: "Find out more about the lenses you might spot in Space Warps."
     news: "Latest News"
@@ -52,6 +53,8 @@
     needles_text_2: "The ideal solution would be to get a computer to look through all of the images, but unfortunately this is not a straightforward solution. Teaching a computer to recognize the effects of gravitational lensing is not too difficult, but they can be easily confused by galaxies that look very similar to a distorted background galaxy. Also in order for the computer to run fast enough to analyse lots of images quickly, they have to cut a lot of corners, and this makes them less effective."
     discovering: "Discovering Lenses: A Human-Computer Partnership"
     discovering_text: "Human beings have a remarkable ability to recognise patterns and detect the unusual with only minimal training. With a basic understanding of what the distorted images of galaxies that have passed through a gravitational lens look like, participants in the SpaceWarps project can help discover new examples of this amazing phenomenon, and enable our survey scientists to carry out new investigations of stars and dark matter in the universe. We will be doing two types of lens search. In our blind searches, we’ll be asking volunteers to spot signs of gravitational lensing in images of the sky taken as part of the CFHT (Canada-France-Hawaii Telescope) legacy survey. This survey has been searched by computers, but we’re pretty sure they didn’t catch all the lenses to be found! These results will help us re-train the computers to do better on larger surveys in future. Then, in our targeted searches, in other upcoming sky surveys, we’ll be showing participants galaxies and groups of galaxies that our computers have selected as possibly being gravitational lenses. The task will then be to assess whether or not they actually are! In both cases, there will be confusing objects around - the challenge is to come up with the most plausible explanation for what is going on, in collaboration with the rest of the SpaceWarps community. Do you think you can spot outer space being warped? We do!"
+    searching: "Searching in Stages"
+    searching_text: "Each lens search has two stages. In the first one, we ask the community to 'spot' gravitational lens systems, with the emphasis on completeness. At this stage, we don't want to miss anything interesting! If in doubt, mark it and discuss it in Talk. In the second stage, 'refine', things are a little different: by this point, we'll have identified (together) several thousand lens candidates, and it's time to start refining this sample to identify the really promising ones. We want to reject the 'false positives', and focus on the objects that really look like lenses. We use the same interface - just with a more critical eye. The goal is to assemble, together, a small list of very good lens candidates for the Science Team to follow up with big telescopes. And big telescope time is not cheap, so we're after the very best lens candidates! You'll know which stage we are on by the color of the website: if it's blue, we're trying to spot the candidates, and if it's orange, we're trying to refine them."
     science_team: "Science Team"
     marshall_bio: "Phil is an astronomer at the University of Oxford Physics department. After deciding he liked doing maths and physics problems at school and university enough to want to make up his own questions, Phil did a PhD at Cambridge University on using gravitational lensing to map the distribution of dark matter in galaxy clusters. He then did several postdoctoral research jobs in California, using strong lenses to weigh galaxies, and investigating automated strong lens detection. He knows very well how hard it is to teach a robot to spot lenses - or at least, how hard it is to teach them not to be confused by large numbers of galaxies that are not lenses."
     verma_bio: "Aprajita is an astronomer at the University of Oxford, and previously worked at Imperial College London and the Max Planck Institute for Extraterrestrial Physics (Germany). She became fascinated by gravitational lenses during her PhD in which she studied some of the most luminous galaxies seen in the Universe, some of which have been gravitationally lensed. Aprajita is interested in making use of gravitational lenses as natural telescopes to study the galaxies that are being lensed in more detail than would normally be possible, as well as measuring the light and dark matter in the lensing galaxies themselves. Having worked with citizen scientists looking for gravitational lenses in Galaxy Zoo, she was inspired by their efforts, motivating the emergence of Space Warps."
@@ -103,13 +106,13 @@
     group_lenses_text_2: "Groups or clusters of massive red galaxies can lens background galaxies into long, bright arcs. These arcs are typically less curved and at larger separations from the lensing galaxies than would be seen for a single lensing galaxy. This is because groups and clusters of galaxies are much more massive than a single galaxy and so the deflection of the image (or separation) can be larger."
     group_lenses_text_3: "Groups and clusters of massive galaxies can form many images, often of more than one background galaxy. The bright images curve around the entire lens that can be several galaxies."

-    rings_spirals: "Rings & Spirals"
+    rings_spirals: "Star-forming galaxies"
     loners: "Loners"
     lightweights: "Lightweights"
     mergers_neighbors: "Mergers & Neighbors"

-    rings_spirals_text_1: "Ring and Spiral galaxies are common galaxy types. These are not massive galaxies but are typically actively forming-stars and therefore the rings and arms look blue. Spiral arms are a common feature, such as found in our own Milky Way. Ring galaxies may be formed by the interaction of two star-forming galaxies. Depending on orientation, spiral arms and rings can look similar to gravitationally lensed images of a background galaxy, these are common false positives. Some tips on how to distinguish them from the real thing are below."
-    rings_spirals_text_2: "These often have yellow "bulges" and blue "spiral arms." Unlike arcs, the arms don't trace a rough circle around the bulge, and is usually accompanied by some fuzzy distribution of light."
+    rings_spirals_text_1: "Star-forming galaxies are common and show a blue component of emission including spiral arms, a ring, or star-forming clumps. These are not massive galaxies but the active star-formation produces a lot of blue light. Spiral arms are a common feature, such as found in our own Milky Way. Ring galaxies may be formed by the interaction of two star-forming galaxies. Irregular and merging galaxies (those without a well defined shape) are often dominated by blue light from star-fromation. Depending on orientation, spiral arms and rings can look similar to gravitationally lensed images of a background galaxy, these are common false positives. They often have red bulges that can mimic a massive lensing galaxy; but the arms or rigns don't have the right shape to be lensed arcs. Sometimes star forming clumps in a galaxy can also be symmetrically arranged arounf the red core of a galaxy and resemble gravitationally lensed quasars. Some tips on how to distinguish these false positives from the real thing are below."
+    rings_spirals_text_2: "These often have yellow "bulges" and blue "spiral arms." Unlike gravitationally lensed arcs, the arms don't trace a rough circle around the bulge, and they are usually accompanied by some fuzzy distribution of light."
     rings_spirals_text_3: "Their yellow bulges are surrounded by complete, oval rings of blue light. In contrast, Einstein Rings are typically more circular, and brighter."

     loners_text_1: "We have put a selection of objects that look like blue arcs in this page, however these are not gravitationally lensed systems. The easiest check is that there is no plausible massive gravitational lens in their vicinity. See below for some examples."
@@ -121,7 +124,7 @@
     lightweights_text_2: "Sometimes a yellow/red galaxy is present near a blue arc but its too small to create such a large or separated arc. The arc might appear too curved or curved in the wrong direction (it doesn't encircle the lensing galaxy). In the case above, the arc is too far away to be lensed by such a small galaxy."
     lightweights_text_3: "The most massive galaxies have bulges of yellow/red stars – bulgeless, blue galaxies usually don't have enough mass to strongly deflect light. Such galaxies are less likely to act as lenses."

-    mergers_neighbors_text_1: "As you may have seen in Galaxy Zoo, galaxies are moving objects that sometime collide and interact. This produces a single more massive galaxy at the end, but as the interaction is very complex and slow, spiral arms and star forming regions from both of the merging galaxies can be tidally distorted and drawn out. Usually, these merging galaxies can be identified due to their similar colors since such galaxies have roughly similar stellar population and ages. Below we give an example of how such mergers can be mistaken to be a lensing galaxy. These arms & tidal tails can end up looking similar to long blue arcs seen in lensing around the central redder bulge of the merging galaxies. Below we give an example of how such mergers can be mistaken to be a lensing galaxy."
-    mergers_neighbors_text_2: "Sometimes two neighbouring galaxies, that aren't physically associated to each other, but are simply next to each other can look like a lens. This is just a chance or random alignment of galaxies. For lensing candidates remember to look for an arc that is defined and curved around a plausible lensing galaxy, and also look for hints of a counter image. If things look very nebulous & fuzzy or curved the wrong way, it's most likely this isn't a lens. See the example below."
-    mergers_neighbors_text_3: "Two galaxies merging together can look a bit like a lens system. Her two spiral galaxies appear to be merging with multiple arms and a fuzzy distribution of light. Sometimes mergers cause displaced arms, or tidal tails. Unlike arcs, the arms or tidal tails do not trace a circle around the red bulge."
-    mergers_neighbors_text_4: "Sometimes two neighbouring galaxies can look a bit like a lens system. In these cases usually the supposed "arc" is not bright enough, it is not clearly defined or is fuzzy or the "lens" is not massive enough (as above)."+    mergers_neighbors_text_1: "As you may have seen in Galaxy Zoo, galaxies are moving objects that sometime collide and interact. This produces a single more massive galaxy at the end, but as the interaction is very complex and slow, spiral arms and star forming regions from both of the merging galaxies can be tidally distorted and drawn out. Usually, these merging galaxies can be identified due to their similar colors since such galaxies have roughly similar stellar population and ages. Below we give an example of how such mergers can be mistaken to be a lensing galaxy. Arms & tidal tails around the central redder bulge of the merging galaxies can end up looking similar to long blue arcs seen in lensing by galaxy groups."
+    mergers_neighbors_text_2: "Sometimes two neighbouring galaxies, that aren't physically associated to each other, but are simply next to each other can look like a lens. This is just a chance or random alignment of galaxies. For lensing candidates remember to look for an arc that is defined and curved around a plausible lensing galaxy, and also look for hints of a counter image. If things look very nebulous & fuzzy or curved the wrong way, it's most likely this isn't a lens. Below we give examples of how such mergers or neighbours can be mistaken to be lensed systems."
+    mergers_neighbors_text_3: "Two galaxies merging together can look a bit like a lens system. Here two spiral galaxies appear to be merging with multiple arms visible and a fuzzy distribution of light. Sometimes mergers cause displaced arms, or tidal tails. Unlike arcs, the arms or tidal tails do not trace a circle around the red bulge."
+    mergers_neighbors_text_4: "Sometimes two neighbouring galaxies can look a bit like a lens system. In these cases, the supposed "arc" is usually not bright enough, it is not clearly defined or is fuzzy or the "lens" is not massive enough (as above)."


Scene: Add missing imports.

Scene: Add missing imports.

--- engine/scene.coffee
+++ engine/scene.coffee
@@ -1,5 +1,5 @@
 {mat2, mat3, mat4, vec2, vec3, vec4, quat} = require 'gl-matrix'
-{PhysicsWorld, set_gravity} = require './physics.coffee'
+{PhysicsWorld, set_gravity, remove_body, destroy_world} = require './physics.coffee'
 {load_scene} = require './loader.coffee'
 {fetch_objects} = require './fetch_assets.coffee'

@@ -91,7 +91,6 @@
         if ob.type=='ARMATURE'
             @armatures.push ob

-
     remove_object: (ob, recursive=true)->
         @children.remove ob
         if not ob.static
@@ -125,6 +124,7 @@
                 child = l-i-1
                 @remove_object children[i]
         return
+
     make_parent: (parent, child, keep_transform=true)->
         if child.parent
             @clear_parent child, keep_transform


Consider undefined CF outcone as CT (fix #165)

--- server/testSystems/codeforces/CodeforcesSubmitDownloader.coffee
+++ server/testSystems/codeforces/CodeforcesSubmitDownloader.coffee
@@ -127,7 +127,7 @@
             if submit.problem.index != @problem
                 logger.info "Skipping submit #{submit.id} because it is for a different problem: #{submit.problem.index} vs #{@problem}"
                 continue
-            outcome = @VERDICTS[submit.verdict] || submit.verdict
+            outcome = @VERDICTS[submit.verdict] || submit.verdict || "CT"
             result.push new Submit(
                 _id: "c" + submit.id,
                 time: new Date(submit.creationTimeSeconds * 1000),


IDE: Pass machine to `FSHelper.createFileInstance`

--- client/ide/lib/index.coffee
+++ client/ide/lib/index.coffee
@@ -502,7 +502,7 @@

     return ->
       path     = "localfile:/Untitled-#{newFileSeed++}.txt@#{Date.now()}"
-      file     = FSHelper.createFileInstance { path }
+      file     = FSHelper.createFileInstance { path, machine: @mountedMachine }
       contents = ''

       @openFile file, contents


onboarding: stop IDE onboarding when VM has changed state

--- client/ide/lib/index.coffee
+++ client/ide/lib/index.coffee
@@ -608,6 +608,11 @@
     @machineStateModal.once 'KDObjectWillBeDestroyed', => @machineStateModal = null
     @machineStateModal.once 'IDEBecameReady', @bound 'handleIDEBecameReady'

+    # stop IDE onboarding if it's running
+    # since machine state is changed and state modal appears,
+    # onboarding throbbers will be unavailable and should be hidden
+    @stopOnboarding()
+

   collapseSidebar: ->

@@ -1565,6 +1570,12 @@
     onboarding.run 'IDELoaded'  if appManager.frontApp is this

+  stopOnboarding: ->
+
+    { onboarding, appManager } = kd.singletons
+    onboarding.stop 'IDELoaded'  if appManager.frontApp is this
+
+
   ###*
    * Update `@layoutMap` for move tab with keyboard shortcuts.
    *


Split sections into paragraphs.

Split sections into paragraphs.

--- client/lib/annotator.coffee
+++ client/lib/annotator.coffee
@@ -4,6 +4,8 @@
 FUZZY_OVERLAP_THRESHOLD = 2.5 # If areas are close enough, we see them as overlapping (in units)
 SECTION_INDENT_THRESHOLD = 1.0 / 40.0
 SPLIT_PARAGRAPH_THRESHOLD = 0.9
+SECTION_PARAGRAPH_DISTANCE_THRESHOLD = 0.5 # To allow for small variations (in units)
+PARAGRAPH_HEIGHT_THRESHOLD = 1.5 # Paragraph has to be larger than one line height

 # Merges sorted lists in sorted list with unique elements
 merge = (list1, list2) ->
@@ -57,10 +59,9 @@
   setTextContent: (pageNumber, textContent) =>
     @_pages[pageNumber - 1].textContent = textContent

-  _dy: (pageNumber, i, j) =>
-    j ?= i + 1
-    page = @_pages[pageNumber - 1]
-    page.textSegments[j].top - page.textSegments[i].top
+  _dy: (pageNumber, i) =>
+    page = @_pages[pageNumber - 1]
+    page.textSegments[i + 1].top - page.textSegments[i].top

   _computeLineHeight: (pageNumber) =>
     page = @_pages[pageNumber - 1]
@@ -127,6 +128,8 @@

     left: left
     top: top
+    right: right
+    bottom: bottom
     width: right - left
     height: bottom - top

@@ -186,7 +189,7 @@

     @_mergeOverlappingSections pageNumber, sections

-  _sectionIndentThreshold: (pageNumber, segmentsIndices) =>
+  _computeSectionProperties: (pageNumber, segmentsIndices) =>
     page = @_pages[pageNumber - 1]

     left = Number.MAX_VALUE
@@ -198,34 +201,43 @@
       left = Math.min left, segment.left
       right = Math.max right, (segment.left + segment.width)

-    left + (right - left) * SECTION_INDENT_THRESHOLD
-
-  _splitParagraphs: (pageNumber, segmentsIndices, sectionIndentThreshold) =>
+    left: left
+    right: right
+    indentThreshold: left + (right - left) * SECTION_INDENT_THRESHOLD
+
+  _splitParagraphs: (pageNumber, segmentsIndices, sectionProperties) =>
     page = @_pages[pageNumber - 1]

     s = 0
     paragraphs = []
     for j in [1...segmentsIndices.length]
       segment = page.textSegments[segmentsIndices[j]]
-
-      if @_dy(pageNumber, segmentsIndices[j - 1], segmentsIndices[j]) > page.lineHeight * SPLIT_PARAGRAPH_THRESHOLD and segment.left > sectionIndentThreshold
-        paragraphs.push segmentsIndices[s...j]
+      paragraphIndices = segmentsIndices[s...j]
+      boundingBox = @_boundingBox pageNumber, paragraphIndices
+
+      if boundingBox.top - segment.top > page.lineHeight * SPLIT_PARAGRAPH_THRESHOLD and segment.left > sectionProperties.indentThreshold
+        paragraphs.push paragraphIndices
         s = j

     paragraphs.push segmentsIndices[s..segmentsIndices.length]
-    console.log paragraphs
     paragraphs

-  _processParagraph: (pageNumber, segmentsIndices) =>
-    page = @_pages[pageNumber - 1]
-
-    page.paragraphs.push @_boundingBox pageNumber, segmentsIndices
+  _processParagraph: (pageNumber, sectionProperties, paragraphIndices) =>
+    page = @_pages[pageNumber - 1]
+
+    boundingBox = @_boundingBox pageNumber, paragraphIndices
+    # TODO: Improve which paragraphs we skip
+    # TODO: We could try to require that right border of the paragraph bounding box is close to section border
+    # TODO: We could try to require that paragraph height is at least 1.5 of the line height
+    # TODO: We could try to require that area covered by segments should be more than 0.95 of the bounding box, so very little empty space
+    if Math.abs(boundingBox.left - sectionProperties.left) < SECTION_PARAGRAPH_DISTANCE_THRESHOLD * SCALE
+      page.paragraphs.push boundingBox

   _processSection: (pageNumber, segmentsIndices) =>
-    sectionIndentThreshold = @_sectionIndentThreshold pageNumber, segmentsIndices
-
-    for segsIs in @_splitParagraphs pageNumber, segmentsIndices, sectionIndentThreshold
-      @_processParagraph pageNumber, segsIs
+    sectionProperties = @_computeSectionProperties pageNumber, segmentsIndices
+
+    for paragraphIndices in @_splitParagraphs pageNumber, segmentsIndices, sectionProperties
+      @_processParagraph pageNumber, sectionProperties, paragraphIndices

   _roundArea: (area) =>
     areaRounded = _.clone area


Fix syntax error

Fix syntax error

--- server/methods.coffee
+++ server/methods.coffee
@@ -35,9 +35,9 @@
         throw new Error("Couldn't download '#{file.url}', status code: #{result.statusCode}")
       else
         break
-    catch
+    catch error
       if numTries >= 3
-        throw
+        throw error

   result.content


delete unnecessary data file

delete unnecessary data file

--- generators/app/templates/src/pug/data.coffee
+++ generators/app/templates/src/pug/data.coffee
@@ -1,133 +0,0 @@
-exports = module.exports =
-  title: 'Thomas Yang'
-  subtitle: 'Feel. Experience. Truth.'
-  description: [
-    'I am a developer and I love design.'
-    'I am curious and I love challenges.'
-    'I draw on screen.'
-    'I spot values from data.'
-    'I make websites that inform and enchant.'
-    'I build programs to avoid repeated works.'
-    'And I am good at these.'
-  ]
-  copyright: 'Made with ♥ by ©Thomas Yang'
-  social:
-    github:
-      link: 'https://github.com/Hacker-YHJ'
-      link_class: 'github'
-      icon_class: 'fa-github'
-    stackoverflow:
-      link: 'http://stackoverflow.com/users/2885344/quarter2twelve'
-      link_class: 'stackoverflow'
-      icon_class: 'fa-stack-overflow'
-    linkedIn:
-      link: 'https://www.linkedin.com/in/thomas-yang-81992a79'
-      link_class: 'linkedin'
-      icon_class: 'fa-linkedin'
-    facebook:
-      link: 'https://www.facebook.com/profile.php?id=100009268239442'
-      link_class: 'facebook'
-      icon_class: 'fa-facebook'
-    pinterest:
-      link: 'https://www.pinterest.com/Thomas_Yang/'
-      link_class: 'pinterest'
-      icon_class: 'fa-pinterest'
-    instagram:
-      link: 'https://www.instagram.com/quarter2twelve/'
-      link_class: 'instagram'
-      icon_class: 'fa-instagram'
-    email:
-      link: 'mailto:jscnm1993@gmail.com'
-      link_class: 'email'
-      icon_class: 'fa-paper-plane'
-  projects:
-    AmazonMovieReviewVis:
-      title: 'Movie Review Vis'
-      description: 'Amazon movie reviews in calendar view,
-                    including sentiment analysis.'
-      img: 'amazon-movie-review-vis'
-      link: '/projects/amazon-movie-review-vis'
-      year: '2016'
-    OneDivPhilographics:
-      title: 'One Div Philographics'
-      description: 'A replica of the Philographics to show
-                    my respect and admiration, using only ONE
-                    div for each graph.'
-      img: 'one_div_philographics'
-      link: '/projects/one-div-philographics'
-      year: '2016'
-    Whatyouvemissedwhenyouarecoding:
-      title: 'WYHMWYAC'
-      description: '''See what's going on in the world while you are coding
-      and making commits to GitHub.
-      We tried to make some crazy coders reflect more on
-      how much beauty they have missed through mindblowing visuals
-      (beautiful photos, floating descriptions, etc).
-      '''
-      img: 'wyhmwyac'
-      link: '/projects/wyvmwyac'
-      year: '2016'
-    HCIProjectPage:
-      title: 'HCI Project Page'
-      description: '''A landing page for my HCI class project --
-      a interactive tool to illuminate basic CS concepts
-      for zero-experienced CS self-learners.
-      '''
-      img: 'goalgo'
-      link: '/projects/hci-tmp'
-      year: '2016'
-    OhMyDearJS:
-      title: 'Oh My Dear JS'
-      description: '''A visualization for bizarre behaviors in
-      Javascript.
-      '''
-      img: 'oh_my_dear_js'
-      link: '/projects/oh-my-dear-js'
-      year: '2016'
-    Contestify:
-      title: 'Contestify'
-      description: '''An online tournament hosting website.
-      '''
-      img: 'contestify'
-      link: 'http://contestify.heroku.com/'
-      year: '2016'
-    paceGame:
-      title: 'PaceGame'
-      description: '''A online platform for exploring and
-      hosting offline boardgame.
-      '''
-      img: 'pace_game'
-      link: 'http://pacegames.herokuapp.com/'
-      year: '2016'
-    keyboardVis:
-      title: 'Keyboard Vis'
-      description: '''A visualization for analyzing key stroke
-      intensity of any text file.
-      '''
-      img: 'key_vis'
-      link: '/projects/keyboard-stroke'
-      year: '2016'
-    minesweeper:
-      title: 'Minesweeper'
-      description: '''Classic minesweeper game.
-      '''
-      img: 'minesweeper'
-      link: '/projects/mines'
-      year: '2014'
-    MigrantVis:
-      title: 'Migrant Vis'
-      description: '''A visualization tool to present an illegal
-      immigrant event made up for IEEE Visual Analytics Science and
-      Technology (VAST) Challenge 2008.
-      '''
-      img: 'migrant_vis'
-      link: '/projects/vis-3'
-      year: '2013'
-    AirlineVis:
-      title: 'Airline Vis'
-      description: '''A visualization tool shows the quantity of
-      airline routes between different places.
-      '''
-      img: 'air_vis'
-      link: '/projects/vis-2'
-      year: '2013'


disable structured data feed syncing and post fetching during tests

--- server/startup.coffee
+++ server/startup.coffee
@@ -53,9 +53,6 @@
         Meteor.clearInterval(interval)
     interval = Meteor.setInterval(validateIncidents, 20000)

-  if not Meteor.isAppTest
-    Meteor.setInterval(autoprocessArticles, 100000)
-
   # If a remote EIDR-C instance url is provided, periodically pull data from it.
   if process.env.ONE_WAY_SYNC_URL
     # Do initial sync on startup
@@ -66,13 +63,16 @@
   Meteor.setInterval updateAutoEvents, 60 * 60 * 1000
   updateAutoEvents()

-  Meteor.setInterval ->
-    # Pull in the latest ProMED posts for processing.
-    Meteor.call('fetchPromedPosts',
-      startDate: moment().subtract(2, 'days').toDate()
-      endDate: new Date()
-    )
-  , 5 * 60 * 60 * 1000
+  if not Meteor.isAppTest
+    Meteor.setInterval(autoprocessArticles, 100000)

-  console.log "Syncing structured data feeds"
-  syncStructuredFeeds()
+    Meteor.setInterval ->
+      # Pull in the latest ProMED posts for processing.
+      Meteor.call('fetchPromedPosts',
+        startDate: moment().subtract(2, 'days').toDate()
+        endDate: new Date()
+      )
+    , 5 * 60 * 60 * 1000
+
+    console.log "Syncing structured data feeds"
+    syncStructuredFeeds()


fix small problem

fix small problem

--- sign-up/signUp.coffee
+++ sign-up/signUp.coffee
@@ -116,6 +116,7 @@
         Meteor.call('accountsCreateUser', username, email, password, (err, data) ->
           if err
             Session.set('entryError', err.reason)
+            $('#signUp .btn').button 'reset'
             return


Remove data that does not have anything

Remove data that does not have anything

--- backbone.tableview.coffee
+++ backbone.tableview.coffee
@@ -153,7 +153,10 @@
         @data.page = 1
         while args.length > 1
             [key, val, args...] = args
-            @data[key] = val
+            if val? and (val == false or val == 0 or val)
+                @data[key] = val
+            else
+                delete @data[key]
         @update()

     # Creates a filter from a filter config definition


go back if the ed2k/magnet is opened in current tab; else close tab.

--- firefox/chrome/content/protocol.coffee
+++ firefox/chrome/content/protocol.coffee
@@ -1,8 +1,9 @@

 url = decodeURIComponent document.location.search.substr(1)

+Ci = Components.interfaces
+
 getTopLevelWindowContext = ->
-	Ci = Components.interfaces
 	window.QueryInterface(Ci.nsIInterfaceRequestor)
 		.getInterface(Ci.nsIWebNavigation)
 		.QueryInterface(Ci.nsIDocShellTreeItem)
@@ -10,8 +11,15 @@
 		.QueryInterface(Ci.nsIInterfaceRequestor)
 		.getInterface(Ci.nsIDOMWindow)

+getBrowser = ->
+	window.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIWebNavigation)
+
 if url
 	getTopLevelWindowContext().XUNLEI_LIXIAN_WEB?.download_url url

-window.close()
+browser = getBrowser()
+if browser.canGoBack
+	browser.goBack()
+else
+	window.close()


Oh, right, gotta return the item value wrapped in a promise.

--- prviewer.coffee
+++ prviewer.coffee
@@ -325,6 +325,8 @@
           for username in pull.reviewers
             userPromises.push makeUserObj(username).then (obj) -> pull.reviewers.push obj
           pull.reviewers = []
+
+          return Q(pull)

     return Q.all(promises) # XXXX do we need Q.all?


models: invitation: cyclomatic complexity reduced to 10

--- workers/social/lib/social/models/invitation.coffee
+++ workers/social/lib/social/models/invitation.coffee
@@ -184,7 +184,7 @@
       (fin) -> JInvitation.one { email, groupName }, fin

     , (invite, fin) ->
-      [fin, invite] = [invite, fin]  unless fin
+      [fin, invite] = paramSwapper invite, fin

       return fin null, no  unless invite
       return invite.remove fin  if forceInvite
@@ -193,29 +193,20 @@
       return fin null, yes

     , (alreadyInvited, fin) ->
-      [fin, alreadyInvited] = [alreadyInvited, fin]  unless fin
+      [fin, alreadyInvited] = paramSwapper alreadyInvited, fin

       return fin null, yes  if alreadyInvited
       isAlreadyMember group, email, fin

     , (alreadyMember, fin) ->
-      [fin, alreadyMember] = [alreadyMember, fin]  unless fin
+      [fin, alreadyMember] = paramSwapper alreadyMember, fin

       return fin null, no  if alreadyMember
       createInviteInstance invitationData, fin

     , (invite, fin) ->
-      unless fin
-        [fin, invite] = [invite, fin]
-        # this is here bc of fucking cyclomatic complexity error of linter
-        # if you put one more if statement here tests will fail
-        # this technically correct but readability suffers
-        # it should be as below - SY
-        #
-        #   [fin, invite] = [invite, fin]  unless fin
-        #   inviteInfo = { email, code: invite.code }  if invite
-        #
-        inviteInfo = { email, code: invite.code }
+      [fin, invite] = paramSwapper invite, fin
+      inviteInfo = { email, code: invite.code }  if invite

       return fin()  if noEmail or not invite
       JInvitation.sendInvitationEmail client, invite, fin
@@ -226,6 +217,12 @@
       return end new KodingError err  if err
       return end null, inviteInfo

+  paramSwapper = (param, fin) ->
+    # this is here bc of fucking cyclomatic complexity error of linter
+    # if you put a few more `if` statements above it will cry
+    # this should be better done inline - SY
+    [fin, param] = [param, fin]  unless fin
+    return [fin, param]

   createInviteInstance = (options, callback) ->


JUser: createGuestUsername: support for sync calls

--- workers/social/lib/social/models/user/index.coffee
+++ workers/social/lib/social/models/user/index.coffee
@@ -654,9 +654,10 @@
         callback null

-  @createGuestUsername = (callback) ->
-
-    callback null, "guest-#{rack()}"
+  @createGuestUsername = (callback = ->) ->
+
+    callback null, username = "guest-#{rack()}"
+    return username

   @fetchGuestUser = (callback)->


Password Change: Return email address did not change error

--- workers/social/lib/social/models/user/index.coffee
+++ workers/social/lib/social/models/user/index.coffee
@@ -717,24 +717,26 @@

     {email} = options

-    @emailAvailable email, (err, res)=>
-
-      if err
-        callback createKodingError "Something went wrong please try again!"
-      else if res is no
-        callback createKodingError "Email is already in use!"
-      else
-        @fetchUser client, (err,user)->
-          account = client.connection.delegate
-          user.changeEmail account, options, callback
-          email = new JMail {
-            email: user.email
-            subject : "Your email has changed"
-            content : """
-    Your email has been changed!  If you didn't request this change, please contact support@koding.com immediately!
-    """
-          }
-          email.save()
+    account = client.connection.delegate
+    account.fetchUser (err, user)=>
+      return callback createKodingError "Something went wrong please try again!" if err
+      if email is user.email then return callback createKodingError "EmailIsSameError"
+      @emailAvailable email, (err, res)=>
+        return callback createKodingError "Something went wrong please try again!" if err
+        if res is no
+          callback createKodingError "Email is already in use!"
+        else
+          @fetchUser client, (err,user)->
+            account = client.connection.delegate
+            user.changeEmail account, options, callback
+            email = new JMail {
+              email: user.email
+              subject : "Your email has changed"
+              content : """
+      Your email has been changed!  If you didn't request this change, please contact support@koding.com immediately!
+      """
+            }
+            email.save()

   @emailAvailable = (email, callback)->
     @count {email}, (err, count)->


Invoke next queue function after login

Invoke next queue function after login

--- workers/social/lib/social/models/user/index.coffee
+++ workers/social/lib/social/models/user/index.coffee
@@ -504,9 +504,11 @@

         # continue login
         else
-          afterLogin user, clientId, session, callback
-
-        queue.next()
+          afterLogin user, clientId, session, (err, response) ->
+
+            callback err, response
+            queue.next()  unless err
+
     , =>
       # temp - sy
       # add loggedin user to the group


Fixed context menu deprecation

Fixed context menu deprecation

--- menus/spark-ide.cson
+++ menus/spark-ide.cson
@@ -3,4 +3,5 @@
     'label': 'Spark Core',
     'submenu': []
   }
-]
+],
+'context-menu': []


Add split commands to context menu

Add split commands to context menu

--- menus/tree-view.cson
+++ menus/tree-view.cson
@@ -33,6 +33,12 @@
     {'label': 'Paste', 'command': 'tree-view:paste'}
     {'type': 'separator'}

+    {'label': 'Split Up', 'command': 'tree-view:open-selected-entry-up'}
+    {'label': 'Split Down', 'command': 'tree-view:open-selected-entry-down'}
+    {'label': 'Split Left', 'command': 'tree-view:open-selected-entry-left'}
+    {'label': 'Split Right', 'command': 'tree-view:open-selected-entry-right'}
+    {'type': 'separator'}
+
     {'label': 'Copy Full Path', 'command': 'tree-view:copy-full-path'}
     {'label': 'Copy Project Path', 'command': 'tree-view:copy-project-path'}
     {'label': 'Open In New Window', 'command': 'tree-view:open-in-new-window'}


Revert "fix createVhost"

Revert "fix createVhost"

This reverts commit f582e606372f4522abb320cbcf68510c5620bf01.

Conflicts:
	kites/sharedHosting/api.coffee

--- kites/sharedHosting/api.coffee
+++ kites/sharedHosting/api.coffee
@@ -225,15 +225,7 @@
   #    else
   #      log.error stderr
   #      callback stderr
-  createVhost : (options,callback)->
-    {username,uid,domainName} = options
-
-    domainName ?= "#{username}.#{config.defaultDomain}"
-    targetPath = "/Users/#{username}/Sites/#{domainName}"
-
-    createDirs = ['-v','-p',targetPath]
-    copyFiles  = ['-v','-r',"#{config.defaultVhostFiles}/website",targetPath]
-    changeOwner = ['-v','-R',"#{uid}:#{uid}","#{targetPath}/website"]
+  createVhost :do->

     spawnWrapper = (command, args , callback)->
       wrapper = spawn command,args
@@ -253,21 +245,30 @@
           log.info info = "createVhost: #{command} done!"
           callback null, info

-
-    spawnWrapper '/bin/mkdir',createDirs, (err,res)=>
-      if err?
-        callback "[ERROR] couldn't create vhost #{err}"
-      else
-        spawnWrapper '/bin/cp',copyFiles,(err,res)=>
-          if err?
-            callback "[ERROR] Culdn't create vhost: #{err}"
-          else
-            spawnWrapper '/bin/chown',changeOwner,(err,res)->
-              if err?
-                callback "[ERROR] Culdn't create vhost: #{err}"
-              else
-                log.info info = "[OK] vhost #{domainName} has been created"
-                callback null, info
+    createVhost = (options,callback)->
+      {username,uid,domainName} = options
+
+      domainName ?= "#{username}.#{config.defaultDomain}"
+      targetPath = "/Users/#{username}/Sites/#{domainName}"
+
+      createDirs = ['-v','-p',targetPath]
+      copyFiles  = ['-v','-r',"#{config.defaultVhostFiles}/website",targetPath]
+      changeOwner = ['-v','-R',"#{uid}:#{uid}","#{targetPath}/website"]
+
+      spawnWrapper '/bin/mkdir',createDirs, (err,res)=>
+        if err?
+          callback "[ERROR] couldn't create vhost #{err}"
+        else
+          spawnWrapper '/bin/cp',copyFiles,(err,res)=>
+            if err?
+              callback "[ERROR] Culdn't create vhost: #{err}"
+            else
+              spawnWrapper '/bin/chown',changeOwner,(err,res)->
+                if err?
+                  callback "[ERROR] Culdn't create vhost: #{err}"
+                else
+                  log.info info = "[OK] vhost #{domainName} has been created"
+                  callback null, info

  # suspendUser : (options,callback)->
  #   #
@@ -375,4 +376,8 @@
  #                 else
  #                   log.debug "[OK] func:unSuspendUser: /usr/sbin/cagefsctl -w #{userToSuspend}"
  #                   res = "[OK] user #{userToSuspend} was successfully unsuspended"
-
+<<<<<<< HEAD
+
+=======
+ #                   log.info res; callback? null, res
+>>>>>>> parent of f582e60... fix createVhost


removed unused element variable

removed unused element variable

--- coffee/client/client.coffee
+++ coffee/client/client.coffee
@@ -21,8 +21,6 @@
       # add a leading 0 if necessary
       if data.address.length < 2
         data.address = "0" + data.address
-      element = $ "[data-address='#{data.address}']" +
-                  "[data-channel='#{data.channel}']"
       if (data.address + '_' + data.channel) of components
         try
           components[data.address + '_' + data.channel].process data


Check if app exists before calling ints quit function, Chris can u check?

--- client/app/MainApp/ApplicationManager.coffee
+++ client/app/MainApp/ApplicationManager.coffee
@@ -30,7 +30,7 @@

   quitApplication:(path)->
     app = @getAppInstance path
-    if typeof app.quit is "function"
+    if app and typeof app.quit is "function"
       app.quit? ->
         @removeAppInstance path
       setTimeout ->


client/payment: add new endpoints to constants

Signed-off-by: Umut Sirin <4055abbc0e40a1254e715b952ccd414607e0b365@gmail.com>

--- client/app/lib/redux/services/payment.coffee
+++ client/app/lib/redux/services/payment.coffee
@@ -15,6 +15,8 @@
   CustomerUpdate     : '/customer/update'
   CustomerDelete     : '/customer/delete'
   CreditCardDelete   : '/creditcard/delete'
+  CreditCardHas      : '/creditcard/has'
+  CreditCardAuth     : '/creditcard/auth'
   InvoiceList        : '/invoice/list'
   Info               : '/info'


No need for removing flag

No need for removing flag

--- client/app/scripts/services/tetromino.coffee
+++ client/app/scripts/services/tetromino.coffee
@@ -19,7 +19,6 @@
           rows: 7
           score_value: 1000
           checking: false
-          removing: false
           in_progress: true
           plummetting_block: false
           sliding_block: false
@@ -128,7 +127,7 @@

       check_for_tetrominos: ->
         return unless @info.in_progress
-        return if @info.checking || @info.removing
+        return if @info.checking
         @info.checking = true
         for block in @blocks
           continue unless block && block.locked && !block.active
@@ -205,7 +204,6 @@

       drop_blocks: ->
         return unless @info.in_progress
-        return if @info.removing
         last_row_x = @info.rows - 1
         for block in @blocks
           continue unless block
@@ -240,15 +238,12 @@

       remove_blocks: (to_remove) ->
         return unless @info.in_progress
-        return if @info.removing
-        @info.removing = true
         ids_to_remove = to_remove.map((b) -> b.id)
         idx = @blocks.length - 1
         while idx >= 0
           if ids_to_remove.indexOf(@blocks[idx].id) > -1
             @blocks.splice(idx, 1)
           idx--
-        @info.removing = false
         @info.current_score += @info.score_value
         @increment_level_if_necessary()
         on_top = @blocks_on_top(to_remove)


clean events code that moved to its own file

--- scripts/phabs_commands.coffee
+++ scripts/phabs_commands.coffee
@@ -43,25 +43,6 @@
     msg.send "hubot-phabs module is version #{pkg.version}"
     msg.finish()

-  # robot.respond (
-  #   /phtest new ([-_a-zA-Z0-9]+)(?::([-_a-zA-Z0-9]+))? ([^=]+)(?: = (.*))?$/
-  # ), (msg) ->
-  #   phab.withPermission msg, msg.envelope.user, 'phuser', ->
-  #     data =
-  #       project: msg.match[1]
-  #       template: msg.match[2]
-  #       title: msg.match[3]
-  #       description: msg.match[4]
-  #       user: msg.envelope.user
-  #     robot.emit 'phab.createTask', data
-
-  robot.on 'phab.createTask', (data) ->
-    phab.createTask data, (res) ->
-      if res.error_info?
-        robot.logger.error res.error_info
-      else
-        robot.logger.info "Task T#{res.id} created = #{res.url}"
-
   #   hubot phab new <project>[:<template>] <name of the task>
   robot.respond (
     /ph(?:ab)? new ([-_a-zA-Z0-9]+)(?::([-_a-zA-Z0-9]+))? ([^=]+)(?: = (.*))?$/


add tests cases for using sampledBy with combined stream

--- spec/BaconSpec.coffee
+++ spec/BaconSpec.coffee
@@ -999,6 +999,23 @@
         src = series(2, [1, 2])
         src.toProperty().sampledBy(src.map(times, 2))
       [1, 2])
+  it "uses updated property after combine", ->
+    latter = (a, b) -> b
+    expectPropertyEvents(
+      ->
+        src = series(2, ["b", "c"]).toProperty("a")
+        combined = Bacon.constant().combine(src, latter)
+        src.sampledBy(combined, add)
+      ["aa", "bb", "cc"])
+  it "uses updated property after combine with subscriber", ->
+    latter = (a, b) -> b
+    expectPropertyEvents(
+      ->
+        src = series(2, ["b", "c"]).toProperty("a")
+        combined = Bacon.constant().combine(src, latter)
+        combined.onValue(->)
+        src.sampledBy(combined, add)
+      ["aa", "bb", "cc"])

 describe "Property.sampledBy(property)", ->
   it "samples property at events, resulting to a Property", ->


Increase timeout

Increase timeout

--- spec/FindFaces.coffee
+++ spec/FindFaces.coffee
@@ -27,7 +27,7 @@
       chai.expect(c.outPorts.faces).to.be.an 'object'

   describe 'when image loaded', ->
-    @timeout 5000
+    @timeout 10000
     canvas = null
     img = null
     beforeEach (done) ->


Displaying nested exceptions.

Displaying nested exceptions.

 * The `bin/tail.coffee` utility can now display nested exceptions.

--- bin/tail.coffee
+++ bin/tail.coffee
@@ -36,6 +36,56 @@
 options.lines or= 4
 options.output or= "text"

+readExceptions = (stderr, exceptions) ->
+  # Split stderr into lines and pop the last blank line.
+  stderr = stderr.split("\n")
+  stderr.pop()
+
+  # Read the stack backwards popping the all the stack lines and the blank line
+  # before the stack.
+  stack = []
+  while stderr.length and match = /^\s{4}at\s(\S+)\s+\(([^:]+):(\d+):(\d+)\)$/.exec(stderr[stderr.length - 1])
+    [ method, file, line, column ] = match.slice(1)
+    stack.unshift { method, file, line, column }
+    stderr.pop()
+
+  # If we have a stack, then we have an exception.
+  if stack.length
+    # Pull the location display off the top of the lines, if it exists.
+    if stderr.length >= 4 and stderr[0] is "" and (location = /^(.*):(\d+)$/.exec(stderr[1])) and (column = /^(\s*)\^$/.exec(stderr[3]))
+      snippet =
+        file: location[1]
+        text: stderr[2]
+        line: parseInt(location[2], 10)
+        column: column[1].length + 1
+      stderr = stderr.slice(4)
+
+    # Read any JSON output, if it exists.
+    json = []
+    if /^\s{4}}/.test(stderr[stderr.length - 2])
+      while not /^\s{4}{$/.test(line = stderr.pop())
+        json.unshift line
+      json.unshift line
+      json = json.join "\n"
+      stderr.pop()
+
+    # What remains is the exception message.
+    message = stderr.join "\n"
+
+    # Build the exception record.
+    exception = { message, stack }
+    if json.length
+      try
+        exception.json = JSON.parse(json)
+      catch e
+        exception.json = json
+        exception.jsonInvalid = true
+    exception.location = snippet if snippet
+
+    exceptions.push exception
+    if exception.json and exception.json.stderr
+      readExceptions(exception.json.stderr, exceptions)
+
 readBuffer = (buffer, end, contents, output, callback) ->
   chunkSize = 1024 * 2
   offset = end - chunkSize
@@ -66,38 +116,9 @@
           record.json = json
           record.jsonInvalid = true
         if record.json.stderr
-          stderr = record.json.stderr.split("\n")
-          stderr.pop() if stderr[stderr.length - 1] is ""
-          stack = []
-          while stderr.length and match = /^\s{4}at\s(\S+)\s+\(([^:]+):(\d+):(\d+)\)$/.exec(stderr.pop())
-            [ method, file, line, column ] = match.slice(1)
-            stack.unshift { method, file, line, column }
-          if stack.length
-            # Pull the location display off the top.
-            if stderr.length >= 4 and stderr[0] is "" and (location = /^(.*):(\d+)$/.exec(stderr[1])) and (column = /^(\s*)\^$/.exec(stderr[3]))
-              snippet =
-                file: location[1]
-                text: stderr[2]
-                line: parseInt(location[2], 10)
-                column: column[1].length + 1
-              stderr = stderr.slice(4)
-            # Ready an JSON context output.
-            json = []
-            if /^\s{4}}/.test(stderr[stderr.length - 1])
-              while not /^\s{4}{$/.test(line = stderr.pop())
-                json.unshift line
-              json.unshift line
-              json = json.join "\n"
-              stderr.pop()
-            message = stderr.join "\n"
-            record.exception = { message, stack }
-            if json.length
-              try
-                record.exception.json = JSON.parse(json)
-              catch e
-                record.exception.json = json
-                record.exception.jsonInvalid = true
-            record.exception.location = snippet if snippet
+          exceptions = []
+          readExceptions(record.json.stderr, exceptions)
+          record.exceptions = exceptions if exceptions.length > 0

       output.unshift record
   if offset > 0 and output.length < options.lines
@@ -114,6 +135,25 @@
       readBuffer buffer, read, contents, output, ->
         readLog fd, buffer, contents, size - read, output, callback

+writeExceptions = (exceptions, message) ->
+  exception = exceptions.shift()
+  process.stdout.write "  #{message} #{new Array((78 - message.length) / 2).join(" -")}\n"
+  if exception.location
+    location = exception.location
+    process.stdout.write "  #{location.file}:#{location.line}.\n"
+    process.stdout.write "  #{location.text}\n"
+    process.stdout.write "  #{new Array(location.column).join(" ")}^\n"
+  process.stdout.write "#{exception.message.replace(/^(\s*\S.*)$/mg, "  $1")}\n"
+  if exception.json
+    exception.json.stderr = "[Nested exception: See below.]" if exceptions.length
+    json = inspect(exception.json, false, 1000).replace(/^(\s*\S.*)$/mg, "    $1")
+    process.stdout.write "#{json}\n"
+  for element in exception.stack
+    process.stdout.write "    at #{element.method} (#{element.file})\n"
+  process.stdout.write "\n"
+  if exceptions.length
+    writeExceptions(exceptions, "Nested exception")
+
 fs.open "/var/log/messages", "r", (error, fd) ->
   throw error if error
   buffer = new Buffer(1024 * 1024 * 64)
@@ -133,21 +173,9 @@
             process.stdout.write "#{header}\n\n"
             process.stdout.write "#{record.message}\n\n"
             if record.json
-              if record.exception
+              if record.exceptions
                 record.json.stderr = "[Uncaught exception: See below.]"
-              json = inspect(record.json, 1000).replace(/^(\s*\S.*)$/mg, "  $1")
+              json = inspect(record.json, false, 1000).replace(/^(\s*\S.*)$/mg, "  $1")
               process.stdout.write "#{json}\n\n"
-            if record.exception
-              process.stdout.write "  Uncaught exception - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n\n"
-              if record.exception.location
-                location = record.exception.location
-                process.stdout.write "  #{location.file}:#{location.line}.\n"
-                process.stdout.write "  #{location.text}\n"
-                process.stdout.write "  #{new Array(location.column).join(" ")}^\n"
-              process.stdout.write "#{record.exception.message.replace(/^(\s*\S.*)$/mg, "  $1")}\n\n"
-              if record.exception.json
-                json = inspect(record.exception.json, 1000).replace(/^(\s*\S.*)$/mg, "    $1")
-                process.stdout.write "#{json}\n\n"
-              for element in record.exception.stack
-                process.stdout.write "    at #{element.method} (#{element.file})\n"
-              process.stdout.write "\n"
+            if record.exceptions
+              writeExceptions(record.exceptions, "Uncaught exception")


Add usage help

Add usage help

--- src/TestRunner.coffee
+++ src/TestRunner.coffee
@@ -17,7 +17,8 @@
   constructor: ->
     log.debug "TestRunner.constructor()"
     @rc = require('rc')("mctr", { #defaults go here.
-     log_level:"debug"
+    help:null
+    log_level:"debug"
     port:4096
     root_url:null
     app_path:null
@@ -29,6 +30,10 @@
     meteor_crashing_text: "=> Your application is crashing. Waiting for file change."
     })
     log.setLevel(@rc.log_level)
+
+    if @rc.help?
+      @usageHelp()
+      process.kill()

     if @rc.root_url is null
      @rc.root_url = "http://localhost:#{@rc.port}/"
@@ -79,5 +84,13 @@
     process.exit code

+  usageHelp : ->
+    process.stdout.setEncoding('utf8');
+    process.stdout.write("Usage: mctr <command>\n\n
+    --app_path [directory]     Send the Meteor app root directory.\n
+    --root_url [address]       Send the root url for Meteor\n
+    --settings_path [json]     Use this json settings file\n
+    --timeout [milliseconds]   Send a timeout for the tests\n
+    --packages [directory]     The package(s) to test\n")

 module.exports = new TestRunner()


more logging

more logging

--- services/real-time/app.coffee
+++ services/real-time/app.coffee
@@ -21,7 +21,8 @@
 CookieParser = require("cookie-parser")

-sessionRedisClient.set "hello-a", "hello-there"
+sessionRedisClient.set "hello-a", "hello-there", (err)->
+	console.log "setting hello-a", err

 DrainManager = require("./app/js/DrainManager")


Wercker fail

Wercker fail

--- extras/CactusFramer/static/app.coffee
+++ extras/CactusFramer/static/app.coffee
@@ -10,4 +10,5 @@

-layer.states.switch "stateA", {curve:"ease-in-out", time:1}+layer.states.switch "stateA", {curve:"ease-in-out", time:1}
+


Workspace: Changed default tab title.

Workspace: Changed default tab title.

--- client/app/CommonViews/workspace/panes/collaborativetabbededitorpane.coffee
+++ client/app/CommonViews/workspace/panes/collaborativetabbededitorpane.coffee
@@ -29,7 +29,7 @@

   createEditorInstance: (file, content, sessionKey) ->
     pane   = new KDTabPaneView
-      name : file?.name or "Untitled.txt"
+      name : file?.name or "untitled.js"

     editor = new CollaborativeEditorPane {
       delegate : @getDelegate()


activity feed: fix bug where duplicate entries occur at end of feed

--- client/landing/app/Applications/Activity.kdapplication/AppController.coffee
+++ client/landing/app/Applications/Activity.kdapplication/AppController.coffee
@@ -126,6 +126,7 @@

   populateActivity:(options = {}, callback=noop)->
     return  if @isLoading
+    return  if @reachedEndOfActivities

     @listController.showLazyLoader no

@@ -201,9 +202,12 @@

   setLastTimestamps:(from, to)->
     # debugger
-    @lastTo   = to
-    @lastFrom = from
-
+
+    if from
+      @lastTo   = to
+      @lastFrom = from
+    else
+      @reachedEndOfActivities = true

   # Store first & last cache activity timestamp.
   extractCacheTimeStamps: (cache)->