Tidy up HistoryV2Manager

Tidy up HistoryV2Manager

--- services/web/public/coffee/ide/history/HistoryV2Manager.coffee
+++ services/web/public/coffee/ide/history/HistoryV2Manager.coffee
@@ -22,8 +22,7 @@

 			@$scope.$on "entity:selected", (event, entity) =>
 				if (@$scope.ui.view == "history") and (entity.type == "doc")
-					# TODO: Set selection.pathname to entity path name
-					# @$scope.history.selection.doc = entity
+					@$scope.history.selection.pathname = _ide.fileTreeManager.getEntityPath(entity)
 					@reloadDiff()

 		show: () ->
@@ -43,7 +42,7 @@
 				atEnd: false
 				selection: {
 					updates: []
-					doc: null
+					pathname: null
 					range: {
 						fromV: null
 						toV: null
@@ -52,6 +51,7 @@
 				diff: null
 			}

+		MAX_RECENT_UPDATES_TO_SELECT: 2
 		autoSelectRecentUpdates: () ->
 			return if @$scope.history.updates.length == 0

@@ -59,7 +59,7 @@

 			indexOfLastUpdateNotByMe = 0
 			for update, i in @$scope.history.updates
-				if @_updateContainsUserId(update, @$scope.user.id)
+				if @_updateContainsUserId(update, @$scope.user.id) or i > @MAX_RECENT_UPDATES_TO_SELECT
 					break
 				indexOfLastUpdateNotByMe = i

@@ -75,7 +75,6 @@
 				.get(url)
 				.then (response) =>
 					{ data } = response
-					console.log "fetchNextBatchOfUpdates", data.updates
 					@_loadUpdates(data.updates)
 					@$scope.history.nextBeforeTimestamp = data.nextBeforeTimestamp
 					if !data.nextBeforeTimestamp?
@@ -86,10 +85,10 @@
 			diff = @$scope.history.diff
 			{updates} = @$scope.history.selection
 			{fromV, toV, pathname} = @_calculateDiffDataFromSelection()
-			console.log "[reloadDiff] current diff", diff
-			console.log "[reloadDiff] new diff data", {fromV, toV, pathname}
-
-			return if !pathname?
+
+			if !pathname?
+				@$scope.history.diff = null
+				return

 			return if diff? and
 				diff.pathname == pathname and
@@ -103,32 +102,24 @@
 				error:    false
 			}

-			# TODO: How do we track deleted files now? We can probably show the diffs easily
-			# with the new system!
-			if true # !doc.deleted
-				diff.loading = true
-				url = "/project/#{@$scope.project_id}/diff"
-				query = ["pathname=#{encodeURIComponent(pathname)}"]
-				if diff.fromV? and diff.toV?
-					query.push "from=#{diff.fromV}", "to=#{diff.toV}"
-				url += "?" + query.join("&")
-
-				@ide.$http
-					.get(url)
-					.then (response) =>
-						{ data } = response
-						diff.loading = false
-						{text, highlights} = @_parseDiff(data)
-						diff.text = text
-						diff.highlights = highlights
-					.catch () ->
-						diff.loading = false
-						diff.error = true
-			else
-				diff.deleted = true
-				diff.restoreInProgress = false
-				diff.restoreDeletedSuccess = false
-				diff.restoredDocNewId = null
+			diff.loading = true
+			url = "/project/#{@$scope.project_id}/diff"
+			query = ["pathname=#{encodeURIComponent(pathname)}"]
+			if diff.fromV? and diff.toV?
+				query.push "from=#{diff.fromV}", "to=#{diff.toV}"
+			url += "?" + query.join("&")
+
+			@ide.$http
+				.get(url)
+				.then (response) =>
+					{ data } = response
+					diff.loading = false
+					{text, highlights} = @_parseDiff(data)
+					diff.text = text
+					diff.highlights = highlights
+				.catch () ->
+					diff.loading = false
+					diff.error = true

 		_parseDiff: (diff) ->
 			row    = 0
@@ -255,14 +246,12 @@

 			selected_pathname = @$scope.history.selection.pathname

-			console.log "[_calculateDiffDataFromSelection]", @$scope.history.selection.updates
 			for pathname, doc of @_perDocSummaryOfUpdates(@$scope.history.selection.updates)
-				console.log "[_calculateDiffDataFromSelection] doc:", pathname, doc
 				if pathname == selected_pathname
 					{fromV, toV} = doc
-					break
-
-			return {fromV, toV, pathname}
+					return {fromV, toV, pathname}
+
+			return {}

 		# Set the track changes selected doc to one of the docs in the range
 		# of currently selected updates. If we already have a selected doc


Use updated project-history response format

--- services/web/public/coffee/ide/history/HistoryV2Manager.coffee
+++ services/web/public/coffee/ide/history/HistoryV2Manager.coffee
@@ -248,7 +248,7 @@
 						updatePathnameWithUpdateVersions(add.pathname, update)
 					if project_op.remove?
 						remove = project_op.remove
-						updatePathnameWithUpdateVersions(remove.pathname, update, remove.atV)
+						updatePathnameWithUpdateVersions(remove.pathname, update, project_op.atV)

 			return docs_summary


make sure response headers are lowercased

--- src/remote/gateway.coffee
+++ src/remote/gateway.coffee
@@ -35,8 +35,12 @@
     timeout = setTimeout delayed, threshold

 reduceHeaders = (headers)->
+  _headers = _.reduce headers, (memo, value, key) ->
+    memo[key.toLowerCase()] = value
+    memo
+  , {}
   _.reduce RESPONSE_HEADERS, (memo, name) ->
-    value = headers[name.toLowerCase()]
+    value = _headers[name.toLowerCase()]
     memo[name] = value if value?
     memo
   , {}
@@ -61,7 +65,7 @@
     headers: headers,
     request: request,
   }
-
+
   return resolve(h)

 class Gateway


Syntax fix for broken assertion.

Syntax fix for broken assertion.

--- spec/javascripts/lib/mobile/autocomplete_mobile_spec.js.coffee
+++ spec/javascripts/lib/mobile/autocomplete_mobile_spec.js.coffee
@@ -227,7 +227,7 @@
           expect(list.tagName).toBe('UL')
           expect(list.className).toBe('autocomplete__results')
           expect(list.childNodes.length).toBe(1)
-          expect(listItem.textContent).toBe(SEARCH_RESULTS[0].title)
+          expect(list.textContent).toBe(SEARCH_RESULTS[0].title)

         it 'should create an unordered list no items when the results list is empty', ->
           list = @myAutoComplete._createList EMPTY_RESULTS


Fixed swf path.

Fixed swf path.

--- src/Ecentinela/ComiconvBundle/Resources/public/js/input.coffee
+++ src/Ecentinela/ComiconvBundle/Resources/public/js/input.coffee
@@ -146,7 +146,7 @@
                 browse_button : 'upload-link'
                 drop_element: 'body'
                 url: Routing.generate 'upload'
-                flash_swf_url: '/bundles/ecentinelacomiconv/Resources/public/plupload/js/plupload.flash.swf'
+                flash_swf_url: '/bundles/ecentinelacomiconv/plupload/js/plupload.flash.swf'
                 max_file_size: MAX_FILE_SIZE
                 filters: [
                     { title: 'Comic files', extensions: 'cbz,zip' }


debug reset routes (use haibu port to restore routes)

--- config/initializers/resetproxy.coffee
+++ config/initializers/resetproxy.coffee
@@ -13,20 +13,22 @@
 resetRoutes = ->
     Application.all (err, installedApps) ->
         appDict = {}
-        for installedApp in installedApps.rows
+        console.log "start"
+        for installedApp in installedApps
             appDict[installedApp.name] = installedApp

         haibuClient.get 'drones/running', (err, res, apps) ->
+            console.log appDict
+
             updateApps(apps, appDict, resetProxy)

-
 updateApps = (apps, appDict, callback) ->
-    app = apps.pop()
     if apps.length > 0
+        app = apps.pop()
         installedApp = appDict[app.name]

         if installedApp? and installedApp.port isnt app.port
-            instaledApp.updateAttributes port: app.port, (err) ->
+            installedApp.updateAttributes port: app.port, (err) ->
                 updateApps apps, appDict, callback
         else
             updateApps apps, appDict, callback


add nothing & fire to analytics

add nothing & fire to analytics

--- app/controllers/subject_viewer.coffee
+++ app/controllers/subject_viewer.coffee
@@ -189,10 +189,14 @@

   onChangeFireCheckbox: ->
     fire = !!@fireCheckbox.attr 'checked'
+    if fire
+      AnalyticsLogger.logEvent 'fire', @classification.id, null, @classification.subject.zooniverseId
     @classification.annotate {fire}, true

   onChangeNothingCheckbox: ->
     nothing = !!@nothingCheckbox.attr 'checked'
+    if nothing
+      AnalyticsLogger.logEvent 'nothing', @classification.id, null, @classification.subject.zooniverseId
     @classification.annotate {nothing}, true

   onClickFinish: ->


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/number_extensions.coffee
+++ src/number_extensions.coffee
@@ -111,7 +111,7 @@
   if min? and max?
     Math.min(Math.max(this, min), max)
   else if min?
-
+    Math.min()
   else if max?

 ###*


remove some logging

remove some logging

--- src/smc-hub/client.coffee
+++ src/smc-hub/client.coffee
@@ -2524,8 +2524,6 @@
                 if not misc.is_object(v?.labels)
                     # what?
                     return
-            dbg('mesg_metrics: ', metric.name)
-            dbg('mesg_metrics: ', metric)
             switch metric.type
                 when 'gauge'
                     metric.aggregator = 'average'


Rewrite patterns for combined-font macros

The state of these patterns is still messy, but they're at least leagues
ahead of the first attempt in terms of accuracy. A third attempt will be
made to rewrite the CFMs once the package's first release is published.

Current issues include:

* Buggy handling of escaped quotes; only some pairs are handled correct.

* Formatting order falling out of sync for some words when more than two
  formatted text-groups are passed.

* No highlighting for escaped characters or punctuation within matches.


Safeguard se não existir CATALOG_SDK

Safeguard se não existir CATALOG_SDK

--- src/coffee/portal-buy-button.coffee
+++ src/coffee/portal-buy-button.coffee
@@ -6,8 +6,9 @@
 # CLASSES
 class BuyButton extends ProductComponent
 	constructor: (@element, @productId, buyData = {}, @options) ->
-		@SDK = CATALOG_SDK
-		@productData = @SDK?.getProductWithVariations(@productId)
+		if CATALOG_SDK?
+			@SDK = CATALOG_SDK
+			@productData = @SDK.getProductWithVariations(@productId)

 		@sku = buyData.sku || null
 		@quantity = buyData.quantity || 1


pass in the event, so we can call stopPropagation

--- core/app/backbone/factlink/trunk8_more_less_mixin.coffee
+++ core/app/backbone/factlink/trunk8_more_less_mixin.coffee
@@ -8,8 +8,8 @@

     @on 'render', @trunk8Render, @

-    @$el.delegate 'a.trunk8-more', 'click', => @trunk8Show()
-    @$el.delegate @_trunk8LessSelector, 'click', => @trunk8Hide()
+    @$el.delegate 'a.trunk8-more', 'click', (e) => @trunk8Show(e)
+    @$el.delegate @_trunk8LessSelector, 'click', (e) => @trunk8Hide(e)

   trunk8Render: ->
     sometimeWhen(


:lipstick: "Private:" is not needed anymore.

--- src/file.coffee
+++ src/file.coffee
@@ -123,7 +123,7 @@
   getDigest: ->
     @digest ? @setDigest(@readSync())

-  # Private: Writes the text to specified path.
+  # Writes the text to specified path.
   #
   # Privilege escalation would be asked when current user doesn't have
   # permission to the path.


Always try to native subscribe

Always try to native subscribe

Retry the subscription in case previous ones fail.

The subscribeToNativeChangeEvents method already guards against a
subscription already existing.

--- src/file.coffee
+++ src/file.coffee
@@ -101,11 +101,9 @@
     @emitter.on('will-throw-watch-error', callback)

   willAddSubscription: =>
-    return if @subscriptionCount++ > 0
+    @subscriptionCount++
     try
       @subscribeToNativeChangeEvents()
-    catch
-      @subscriptionCount--

   didRemoveSubscription: =>
     @subscriptionCount--


Add users to fixture

Add users to fixture

--- db/fixtures/users.coffee
+++ db/fixtures/users.coffee
@@ -121,4 +121,8 @@
 ,
   name: "Headbanger"
   group: "dreadnok"
+,
+  name: "Red Riding Hood"
+,
+  name: "Moonshine"
 ]


:lipstick:

:lipstick:

--- src/packages/markdown-preview/lib/markdown-preview-view.coffee
+++ src/packages/markdown-preview/lib/markdown-preview-view.coffee
@@ -91,9 +91,8 @@

   renderMarkdown: ->
     @setLoading()
-    roaster(@buffer.getText(), {}, (err, html) =>
+    roaster @buffer.getText(), {}, (err, html) =>
       if err
         @setErrorHtml(err)
       else
         @html(@tokenizeCodeBlocks(html))
-    )


Fire one cannonball at a time

Fire one cannonball at a time

--- src/game.coffee
+++ src/game.coffee
@@ -85,7 +85,8 @@

   constructor: (@x, @y, @game)->
     @radius = 10
-    super @x, @y, @radius, @radius, 50, @game, 1
+    @speed  = 10
+    super @x, @y, @radius, @radius, @speed, @game, 1

   update: ->
     super
@@ -118,8 +119,6 @@

     @controller = new Controller @
     @player     = new Player 0, 0, 16, 100, 5, @
-
-  keys: ->

   timestamp: ->
     window.performance.now()
@@ -162,6 +161,7 @@
     @projectiles.push projectile

   removeProjectile: (projectile)->
+    @projectiles.splice @projectiles.indexOf(projectile), 1

   canFire: ->
     @projectiles.length is 0


CUI.Tooltip

CUI.Tooltip

- fix showTimeout: return a Promise, like CUI.Layer.showTimeout does
- use proper "ms" value, not always the default @_show_ms

--- src/elements/Tooltip/Tooltip.coffee
+++ src/elements/Tooltip/Tooltip.coffee
@@ -114,10 +114,10 @@
 		if Tooltip.current
 			Tooltip.current.hide(ev)
 			@show(ev)
+			return CUI.resolvedPromise()
 		else
 			Tooltip.current = @
-			super(null, ev)
-		@
+			return super(ms, ev)

 	hideTimeout: (ms=@_show_ms, ev) ->
 		Tooltip.current = null


:bug: Show search panel on toggling too

:bug: Show search panel on toggling too

--- src/integrations/atom/panels.coffee
+++ src/integrations/atom/panels.coffee
@@ -18,7 +18,7 @@
     dblEscOnSearchInput = searchPanel.keyDownStreams.esc.bufferWithTimeOrCount(300, 2).filter R.propEq('length', 2)
     @add dblEscOnSearchInput.onValue R.pipe(@hideItemsPanel, @hideSearchPanel, @activateTextEditor)

-    @add atoms.cancelCommand().onValue R.pipe(@showItemsPanel, @getSearchInput, @selectAndFocus)
+    @add atoms.cancelCommand().onValue R.pipe(@showItemsPanel, @showSearchPanel, @getSearchInput, @selectAndFocus)

   selectAndFocus: (input) ->
     input.select()
@@ -37,7 +37,10 @@
     @itemsPanel.hide()

   showItemsPanel: ->
-    @itemsPanel.show() unless @itemsPanel.isVisible()
+    @itemsPanel.show()
+
+  showSearchPanel: ->
+    @searchPanel.show()

   createSearchPanel: (el) ->
     unless @itemsPanel


Allow updating ngIdle idleDuration

Allow updating ngIdle idleDuration

--- app/js/controllers/preferences.coffee
+++ app/js/controllers/preferences.coffee
@@ -1,8 +1,9 @@
-angular.module("app").controller "PreferencesController", ($scope, $location, Wallet, Shared, Growl) ->
+angular.module("app").controller "PreferencesController", ($scope, $location, Wallet, Shared, Growl, $idle) ->
     $scope.timeout = Wallet.timeout
-
     $scope.updatePreferences = ->
+        Wallet.timeout = $scope.timeout
+        $idle._options().idleDuration=Wallet.timeout
+        console.log($idle._options())
         Wallet.set_setting('timeout', $scope.timeout).then (r) ->
             console.log('t sumbitted')
-            Shared.timeout=$scope.timeout
             Growl.notice "", "Preferences Updated"


Initial #hash navigation for easier debugging

--- web2/js/stores/navigation.coffee
+++ web2/js/stores/navigation.coffee
@@ -6,8 +6,11 @@

         @register flux.getActions('navigation').setPage, @handlePage

+        if window.location.hash?
+            page = window.location.hash.substring 1
+
         @state =
-            page: 'poi'
+            page: page ? 'poi'

     handlePage: (page) ->


Keep upgrading deprecated selectors until 1.0

--- src/styles-element.coffee
+++ src/styles-element.coffee
@@ -1,5 +1,4 @@
 {Emitter, CompositeDisposable} = require 'event-kit'
-{includeDeprecatedAPIs} = require 'grim'

 class StylesElement extends HTMLElement
   subscriptions: null
@@ -19,7 +18,7 @@
     @styleElementClonesByOriginalElement = new WeakMap

   attachedCallback: ->
-    if includeDeprecatedAPIs and @context is 'atom-text-editor'
+    if @context is 'atom-text-editor'
       for styleElement in @children
         @upgradeDeprecatedSelectors(styleElement)
     @initialize()
@@ -67,7 +66,7 @@

     @insertBefore(styleElementClone, insertBefore)

-    if includeDeprecatedAPIs and @context is 'atom-text-editor'
+    if @context is 'atom-text-editor'
       @upgradeDeprecatedSelectors(styleElementClone)

     @emitter.emit 'did-add-style-element', styleElementClone


element -> node (for strings)

element -> node (for strings)

--- src/trix/text_view.coffee
+++ src/trix/text_view.coffee
@@ -38,8 +38,8 @@

     # Add an extra newline if the text ends with one. Otherwise, the cursor won't move down.
     if @text.endsWith("\n")
-     element = createElementsForString("\n", @text.getLength())[0]
-     containers.push(element)
+     node = createNodesForString("\n", @text.getLength())[0]
+     containers.push(node)

     containers

@@ -118,28 +118,28 @@
       element.style["background-color"] = "highlight" if attributes.selected

     if string
-      for child in createElementsForString(string, position)
-        element.appendChild(child)
+      for node in createNodesForString(string, position)
+        element.appendChild(node)

     element

-  createElementsForString = (string, position) ->
-    elements = []
+  createNodesForString = (string, position) ->
+    nodes = []

     for substring, index in string.split("\n")
       if index > 0
         node = document.createElement("br")
         node.trixPosition = position
         position += 1
-        elements.push(node)
+        nodes.push(node)

       if substring.length
         node = document.createTextNode(preserveSpaces(substring))
         node.trixPosition = position
         position += substring.length
-        elements.push(node)
+        nodes.push(node)

-    elements
+    nodes

   preserveSpaces = (string) ->
     string


Update remote-ftp.cson

Update remote-ftp.cson

--- keymaps/remote-ftp.cson
+++ keymaps/remote-ftp.cson
@@ -3,3 +3,4 @@
   'ctrl-alt-d': 'remote-ftp:disconnect'
   'ctrl-alt-c': 'remote-ftp:connect'
   'cmd-shift-u': 'remote-ftp:upload-selected'
+  'f5': 'remote-ftp:download-active'


updated weights from new measurements

updated weights from new measurements

--- assets/coffeescript/product_list.coffee
+++ assets/coffeescript/product_list.coffee
@@ -53,7 +53,7 @@
     cartlabel: "Eye camera: no eye camera"
     cartSpecs: "no eye camera"
     dimensions: 0
-    weight: 27 # 12 headset frame, 7 harness cable, 8 cable clip weight
+    weight: 26 # 12 headset frame, 2 harness cable, 12 cable clip weight
     img: "../media/images/config_images/pupil-config-eye-none.jpg"
     video: "#"
     order: 1
@@ -68,7 +68,7 @@
         latency: "35.6ms"
     cartlabel: "Eye camera: 30hz eye camera"
     cartSpecs: "Resolution: 640x480 @30fps. IR illumination."
-    dimensions: 30 # 12 headset frame, 7 harness cable, 8 cable clip weight, 3 eye camera
+    dimensions: 30 # 12 headset frame, 2 harness cable, 12 cable clip weight, 3 eye camera (add 1g for just in case)
     weight: 300
     img: "../media/images/config_images/pupil-config-eye-30hz.jpg"
     video: "#"
@@ -85,7 +85,7 @@
     cartlabel: "Eye camera: 120hz eye camera"
     cartSpecs: "Resolution: 640x480 @120fps. IR illumination."
     dimensions: 0
-    weight: 31 # 12 headset frame, 7 harness cable, 8 cable clip weight, 4 eye camera
+    weight: 31 # 12 headset frame, 2 harness cable, 12 cable clip weight, 4 eye camera (add 1g for just in case)
     img: "../media/images/config_images/pupil-config-eye-120hz.jpg"
     video: "#"
     order: 3
@@ -101,7 +101,7 @@
     cartlabel: "Eye cameras: 120hz binocular eye cameras"
     cartSpecs: "2 eye cameras. Resolution: 640x480 @120fps. IR illumination."
     dimensions: 0
-    weight: 40 # 16 headset frame, 8 harness cable ESTIMATE, 10 cable clip weight, 2x3 eye camera
+    weight: 40 # 16 headset frame, 2 harness cable, 16 cable clip weight, 2x3 eye camera
     img: "../media/images/config_images/pupil-config-eye-120hz-binocular.jpg"
     video: "#"
     order: 4


hub/remmeber_me has to set the hub when sending back the signed_in_mesg -- #5630

--- src/packages/hub/client.coffee
+++ src/packages/hub/client.coffee
@@ -326,6 +326,8 @@
                     return
                 # sign them in if not already signed in
                 if @account_id != signed_in_mesg.account_id
+                    # DB only tells us the account_id, but the hub might have changed from last time
+                    signed_in_mesg.hub = @_opts.host + ':' + @_opts.port
                     @hash_session_id   = hash
                     @signed_in(signed_in_mesg)
                     @push_to_client(signed_in_mesg)


Only use long option for set portable

Only use long option for set portable

--- src/browser/main.coffee
+++ src/browser/main.coffee
@@ -109,11 +109,11 @@
   options.boolean('profile-startup').describe('profile-startup', 'Create a profile of the startup execution time.')
   options.alias('r', 'resource-path').string('r').describe('r', 'Set the path to the Atom source directory and enable dev-mode.')
   options.boolean('safe').describe('safe', 'Do not load packages from ~/.atom/packages or ~/.atom/dev/packages.')
+  options.boolean('set-portable').describe('set-portable', 'Set portable mode. Copies the ~/.atom folder to be a sibling of the installed Atom location.')
   options.alias('t', 'test').boolean('t').describe('t', 'Run the specified specs and exit with error code on failures.')
   options.string('timeout').describe('timeout', 'When in test mode, waits until the specified time (in minutes) and kills the process (exit code: 130).')
   options.alias('v', 'version').boolean('v').describe('v', 'Print the version.')
   options.alias('w', 'wait').boolean('w').describe('w', 'Wait for window to be closed before returning.')
-  options.alias('p', 'set-portable').boolean('p').describe('p', 'Set portable mode.')
   options.string('socket-path')

   args = options.argv


Revert parseCommandLine movement

Revert parseCommandLine movement

--- src/browser/main.coffee
+++ src/browser/main.coffee
@@ -12,13 +12,14 @@
 console.log = require 'nslog'

 start = ->
-  args = parseCommandLine()
-  args.atomHome = setupAtomHome()
+  setupAtomHome()
   setupCompileCache()
   return if handleStartupEventWithSquirrel()

   # NB: This prevents Win10 from showing dupe items in the taskbar
   app.setAppUserModelId('com.squirrel.atom.atom')
+
+  parseCommandLine()

   addPathToOpen = (event, pathToOpen) ->
     event.preventDefault()


fixed error where & caused favicon api to crash

--- app/scripts/directives/d3Donut.coffee
+++ app/scripts/directives/d3Donut.coffee
@@ -15,7 +15,8 @@
         if scope.contributor
           scope.percent = (scope.contributor.total_amount / scope.scale)
           # get favicon url from contributor name
-          ApiGet.own "favicon?company=\"#{scope.contributor.name}\"", scope.draw_favicon, this
+          company = scope.contributor.name.replace(/&/i, "and")
+          ApiGet.own "favicon?company=\"#{company}\"", scope.draw_favicon, this
           scope.drawD3()

       # on window resize, redraw d3 canvas


More height and plural text

More height and plural text

--- app/scripts/lazerscripts/lunch.coffee
+++ app/scripts/lazerscripts/lunch.coffee
@@ -79,7 +79,7 @@
       @parallel(
         @sequence(
           @wait 3000
-          @say 'Enemies', 'Get him!'
+          @say 'Enemies', 'Get them!'
         )
         @nextSlide @sequence(
           @placeSquad Game.Scripts.Slider,
@@ -116,7 +116,7 @@
       @nextSlide()
       @updateTitle 'Vertical motion'
       @parallel(
-        @gainHeight 600, duration: 10000
+        @gainHeight 800, duration: 10000
         @placeSquad Game.Scripts.Sine,
           amount: 8
           delay: 1000
@@ -124,7 +124,7 @@
       @nextSlide()
       @updateTitle 'Bezier, powerups'
       @parallel(
-        @gainHeight -600, duration: 10000
+        @gainHeight -800, duration: 10000
         @nextSlide @sequence(
           @placeSquad Game.Scripts.PresentationSwirler,
             drop: 'xp'


fixing extra newline in the prelude

fixing extra newline in the prelude

--- nodes.coffee
+++ nodes.coffee
@@ -259,7 +259,7 @@
         exp
       rest = @expressions[preludeExps.length...]
       @expressions = preludeExps
-      prelude = "#{@compileNode o}\n\n" if preludeExps.length
+      prelude = "#{@compileNode o}\n" if preludeExps.length
       @expressions = rest
       # We assume that we will need the safety wrapper.
       # This is our best guess without actually compiling.


fixed a bug where postfix `?` was incorrectly unwrapping its operand

--- nodes.coffee
+++ nodes.coffee
@@ -1216,7 +1216,7 @@
   constructor: (@expression) ->

   compileNode: (o) ->
-    code = @expression.compile o
+    code = @expression.compile o, LEVEL_OP
     code = if IDENTIFIER.test(code) and not o.scope.check code
       if @negated
         "typeof #{code} == \"undefined\" || #{code} === null"


ClampBounds docs

ClampBounds docs

--- src/clamp_bounds.coffee
+++ src/clamp_bounds.coffee
@@ -1,6 +1,6 @@
 ###*
 The ClampBounds module adds a check to make sure
-that the including GameObject doesn't
+that the including GameObject doesn't move out

 # Health regeneration


More tweaks to Slice#compileNode

More tweaks to Slice#compileNode

--- nodes.coffee
+++ nodes.coffee
@@ -677,11 +677,7 @@
       else if SIMPLENUM.test compiled
         (+compiled + 1).toString()
       else
-        [definition, reference] = to.cache o, LEVEL_PAREN
-        if reference is definition
-          "#{reference} + 1 ? #{reference} + 1 : 9e9"
-        else
-          "(#{definition} + 1) ? #{reference} : 9e9"
+        "(#{compiled} + 1) || 9e9"
     ".slice(#{ fromStr }#{ toStr or '' })"

 #### Obj


Proper error handling

Proper error handling

--- db-test/rest.coffee
+++ db-test/rest.coffee
@@ -12,11 +12,12 @@
     app.set 'x-powered-by', false
     app.use bodyParser.json()

-    app.use (err, req, res, next) ->
-        console.error err
-        res
-            .status 500
-            .send ':-('
+    async = (f) ->
+        Promise.coroutine (req, res, next) ->
+            try
+                yield from f req, res, next
+            catch err
+                next err

     app.use Promise.coroutine (req, res, next) ->
         if req.method in ['GET', 'HEAD', 'OPTIONS']
@@ -29,32 +30,42 @@
             .status 403
             .send 'Valid API token required'

-    app.get '/geocaches', Promise.coroutine (req, res, next) ->
+    app.get '/geocaches', async (req, res, next) ->
         res.set 'Content-Type', 'application/json'
         geocacheStream = yield geocacheService.getStream req.query, true
         geocacheStream
             .pipe JSONStream.stringify('[', ',', ']')
             .pipe res

-    app.get '/geocache/:gc', Promise.coroutine (req, res, next) ->
+    app.get '/geocache/:gc', async (req, res, next) ->
         geocache = yield geocacheService.getSingle req.params.gc
         res.json geocache

-    app.put '/geocaches', Promise.coroutine (req, res, next) ->
-        yield geocacheService.upsertBulk req.body
+    app.put '/geocaches', async (req, res, next) ->
+        models = if typeof req.body is 'array'
+            req.body
+        else
+            [req.body]
+
+        yield geocacheService.upsertBulk models
         res.status 201
         res.send ''

-    app.delete '/geocaches', Promise.coroutine (req, res, next) ->
+    app.delete '/geocaches', async (req, res, next) ->
         yield geocacheService.deleteAll()
         res.status 202
         res.send ''

-    app.get '/gcs', Promise.coroutine (req, res, next) ->
+    app.get '/gcs', async (req, res, next) ->
         res.set 'Content-Type', 'application/json'
         geocacheStream = yield geocacheService.getStream req.query, false
         geocacheStream
             .pipe JSONStream.stringify('[', ',', ']')
             .pipe res

+    app.use (err, req, res, next) ->
+        console.error err
+        res.status 500
+        res.send ':-('
+
     return app


only the test output is printed to the console

--- src/deploy/gulp-espruino.coffee
+++ src/deploy/gulp-espruino.coffee
@@ -62,7 +62,9 @@

       serialPort.open ->
         executor.execute("reset();\n")
+          .then(-> executor.execute("echo(0);\n"))
           .then(-> executor.execute("{ #{chunk.contents.toString()} }\n"))
+          .then(-> executor.execute("echo(1);\n"))
           .then(-> executor.execute("save();\n"))
           .then(-> publish.content(output.all() unless publish.published()))


Remove menus

Remove menus

--- menus/notifications.cson
+++ menus/notifications.cson
@@ -1,22 +0,0 @@
-# See https://atom.io/docs/latest/creating-a-package#menus for more details
-'context-menu':
-  'atom-text-editor': [
-    {
-      'label': 'Toggle notifications'
-      'command': 'notifications:toggle'
-    }
-  ]
-'menu': [
-  {
-    'label': 'Packages'
-    'submenu': [
-      'label': 'notifications'
-      'submenu': [
-        {
-          'label': 'Toggle'
-          'command': 'notifications:toggle'
-        }
-      ]
-    ]
-  }
-]


context menu item for open help file

context menu item for open help file

--- menus/supercollider.cson
+++ menus/supercollider.cson
@@ -2,6 +2,7 @@
 'context-menu':
   '.overlayer':
     'SuperCollider: Evaluate selection': 'supercollider:eval'
+    'SuperCollider: Open help file': 'supercollider:open-help-file'

 'menu': [
   {


Bettter numbers on root page

Bettter numbers on root page

--- client/components/Root.coffee
+++ client/components/Root.coffee
@@ -42,7 +42,7 @@
                         a <Col xs={12} sm={6} md={4} lg={4} key={index}>

-                                    {index.toString(2)}
+                                    {index.toString(2).replace(/0/g, "○").replace(/1/g, "●")}

                                 {d}


Push fix

Push fix

--- client/views/developer.coffee
+++ client/views/developer.coffee
@@ -8,7 +8,7 @@
   fetch = modfetch.findOne
     _id: Router.current().options.params.id
   return if !fetch?
-  fetch.status isnt 0
+  fetch.status isnt 1
 Template.developer.events
   "click .servt tr": ->
     Router.go Router.routes["fetchDetail"].path({id: @_id})
@@ -25,7 +25,7 @@
   kv
 Template.fetchDetail.events
   "click .createLobBtn": ->
-    Meteor.call "devCreateLobby", @_id, (err, res)->
+    Meteor.call "devCreateLobby", @fetch, (err, res)->
       if err?
         if err.error is 401
           Router.go "/install/#{err.reason}"
@@ -37,7 +37,7 @@
   "click .delBtn": ->
     bootbox.confirm "Are you sure you want to delete this mod?", (res)=>
       return if !res
-      Meteor.call "delMod", @_id, (err, res)->
+      Meteor.call "delMod", @fetch, (err, res)->
         if err?
           $.pnotify
             title: "Can't Delete"
@@ -49,9 +49,9 @@
             text: "Mod has been deleted."
             type: "success"
   "click .pubBtn": ->
-    Meteor.call "flipPublic", @_id
+    Meteor.call "flipPublic", @fetch
   "click .dbBtn": ->
-    Meteor.call "flipPlayable", @_id
+    Meteor.call "flipPlayable", @fetch
   "click .udBtn": ->
     Router.go Router.routes["newFetch"].path({id: @_id})
   "click .ftBtn": ->


correct typo; key should be answer_id

correct typo; key should be answer_id

--- coach/src/exercise/index.cjsx
+++ coach/src/exercise/index.cjsx
@@ -68,7 +68,7 @@
         eventData = change: step, data: step, status: 'saving'

         channel.emit("change.#{step.id}", eventData)
-        api.channel.emit("exercise.#{step.id}.save.send", id: step.id, _.pick(step, 'answer_idr'))
+        api.channel.emit("exercise.#{step.id}.save.send", id: step.id, _.pick(step, 'answer_id'))

       setFreeResponseAnswer: (id, freeResponse) ->
         step.free_response = freeResponse


Back to dialog 0 when `check_inactivity` detects inactivity.

--- src/main.coffee
+++ src/main.coffee
@@ -106,9 +106,8 @@
     clearInterval last_check_inactivity_interval
     check_inactivity = ->
         time = (Date.now() - log.last_activity_change.date)
-        if ewin.isVisible() and current_dialog == 0
-            log.new_entry {active:false, date:show_window_time}
-            render_all()
+        set_dialog 0
+        log.new_entry {active:false, date:show_window_time}
         ui_alarm()

     last_check_inactivity_interval = setInterval check_inactivity,


fixed sys dependency

fixed sys dependency

--- lib/coffee/yalog.coffee
+++ lib/coffee/yalog.coffee
@@ -1,18 +1,19 @@

-sys = require('sys')
-fs  = require('fs')
+util = require('util')
+fs   = require('fs')

 _ = require 'underscore'

 config = require './config'
 color  = require './color'
 helper = require './helper'
+
 exports.helper = helper

 # initalize with empty inputs
 exports._status = status = {
-  logger       : sys.puts
-  loggerContext: sys
+  logger       : util.puts
+  loggerContext: util
   file         : null
   color        : process.env.TERM?.indexOf('color')  >= 0
   separator    : " "


NEW-FUNC Template can be also function and template is overwrite by route when is false in prototype

--- src/neck.coffee
+++ src/neck.coffee
@@ -54,8 +54,9 @@
     if @parent
       @listenTo @parent, 'remove', @remove
       @listenTo @parent, 'clear', @clear
-
-    @template = opts.template if opts.template
+
+    if opts.template and not @template
+      @template = opts.template

     if @template is true
       @template = @$el.html()
@@ -82,9 +83,12 @@
     @trigger 'clear' # Remove childs listings

     if @template
-      if typeof (template = Neck.DI.load(@template, type: 'template')) is 'function'
+      unless typeof @template is 'function'
+        if typeof (template = Neck.DI.load(@template, type: 'template')) is 'function'
+          template = template @scope
+      else
         template = template @scope
-
+
       if @divWrapper
         @$el.html template
       else
@@ -141,7 +145,7 @@

   _setAccessor: (key, value, controller = @parent)->
     scope = controller.scope
-    console.log [value, resolves] = @_parseValue value
+    [value, resolves] = @_parseValue value

     options = enumerable: true, get: ->
       try


Hide null data layer from UI

Hide null data layer from UI

--- src/p13n.coffee
+++ src/p13n.coffee
@@ -469,7 +469,8 @@
         getDataLayers: ->
             layers = []
             ALLOWED_VALUES.data_layer.map (layerName) =>
-                layers.push {name: layerName, selected: @get('data_layer') == layerName}
+                unless layerName == null
+                    layers.push {name: layerName, selected: @get('data_layer') == layerName}
                 return
             layers


Remove inline KONFIG environment variables

Signed-off-by: Sonmez Kartal <d2d15c3d37396a5f2a09f8b42cf5f27d43a3fbde@koding.com>

--- config/generateRunFile.coffee
+++ config/generateRunFile.coffee
@@ -18,22 +18,6 @@
       str += "$#{key}pid "

     return str
-
-  envvars = (options = {}) ->
-    options.exclude or= []
-
-    env = ''
-
-    add = (name, value) ->
-      env += "export #{name}=${#{name}:-#{JSON.stringify value}}\n"
-
-    for name, value of KONFIG.ENV when name not in options.exclude
-      add name, value
-
-    add 'GOPATH', '$KONFIG_PROJECTROOT/go'
-    add 'GOBIN', '$GOPATH/bin'
-
-    return env

   workerList = (separator = ' ') ->
     (key for key, val of KONFIG.workers).join separator
@@ -108,8 +92,7 @@
   run = """
     #!/bin/bash

-    # ------ THIS FILE IS AUTO-GENERATED ON EACH BUILD ----- #\n
-    #{envvars()}
+    # ------ THIS FILE IS AUTO-GENERATED ON EACH BUILD ----- #

     mkdir $KONFIG_PROJECTROOT/.logs &>/dev/null

@@ -172,20 +155,6 @@
           echo -e "\n\nPlease do ./run again\n"
           exit 1;
       fi
-    }
-
-    function printconfig () {
-      if [ "$2" == "" ]; then
-        cat << EOF
-        #{envvars({ exclude:["KONFIG_JSON"] })}EOF
-      elif [ "$2" == "--json" ]; then
-
-        echo '#{KONFIG.JSON}'
-
-      else
-        echo ""
-      fi
-
     }

     function migrations () {
@@ -699,7 +668,6 @@
   return """
     #!/bin/bash
     export HOME=/home/ec2-user
-    export KONFIG_JSON='#{KONFIG.JSON}'

     COMMAND=$1
     shift


Form confirmation on submit, not on click.

--- confirm_with_reveal.js.coffee
+++ confirm_with_reveal.js.coffee
@@ -96,9 +96,11 @@

 else

-  $(document).on 'click', '[data-confirm]', (e) ->
+  confirm_handler = (e) ->
     proceed = reveal_confirm $(this)
     if !proceed
       e.preventDefault()
       e.stopImmediatePropagation()
     proceed
+  $(document).on 'click', 'a[data-confirm], :input[data-confirm]', confirm_handler
+  $(document).on 'submit', 'form[data-confirm]', confirm_handler


Define bound in KDObject internally since we don't have Bongo in fw

--- core/KDObject.coffee
+++ core/KDObject.coffee
@@ -20,7 +20,13 @@
     @on 'error', error
     @once 'ready', => @readyState = READY

-  bound: Bongo.bound
+  bound: (method)->
+    throw new Error "@bound: unknown method! #{method}"  unless @[method]?
+    boundMethod = "__bound__#{method}"
+    boundMethod of this or Object.defineProperty(
+      this, boundMethod, value: @[method].bind this
+    )
+    return @[boundMethod]

   lazyBound: (method, rest...)-> @[method].bind this, rest...


fix updating correct model; period/course was reversed

--- tutor/src/components/task-plan/builder/tasking-date-times.cjsx
+++ tutor/src/components/task-plan/builder/tasking-date-times.cjsx
@@ -37,8 +37,8 @@

   setDefaultTime: (timeChange) ->
     {courseId, period} = @props
-    model = if period then Courses.get(courseId) else period
-    model.time_zone = timeChange
+    model = if period then period else Courses.get(courseId)
+    _.assign(model, timeChange)
     model.save()

   isSetting: ->


Remove debug console.log

Remove debug console.log

--- src/provider/streamable.coffee
+++ src/provider/streamable.coffee
@@ -23,7 +23,6 @@
                 when /webm/.test(key) then 'video/webm'
                 else null

-            console.log contentType
             if contentType is null
                 continue


Clear out the comment after it has been posted.

--- app/assets/volume/slot.coffee
+++ app/assets/volume/slot.coffee
@@ -806,6 +806,7 @@
       slot.postComment {text:message}, getSelection(), replyTo?.comment.id
       .then (c) ->
           slot.getSlot(slot.segment, ['comments']).then((res) ->
+              $scope.form.comment.text = ''
               $scope.comments = (new Comment(comment) for comment in res.comments)
             , (res) ->
                 messages.addError


Refactor app.Firebase.

Refactor app.Firebase.

--- app/client/js/firebase.coffee
+++ app/client/js/firebase.coffee
@@ -41,23 +41,28 @@
   ###
   onSimpleLogin: (error, user) ->
     if error
-      # TODO(steida): Report to server.
-      console.log error
-      return
+      @onUserError error
+    else if !user
+      @onUserLogout()
+    else
+      @onUserLogin user

-    if !user
-      # NOTE(steida): Stop listening changes after logout.
-      @userRef?.off 'value'
-      # PATTERN(steida): Logout deletes all user data in local storage.
-      # TODO(steida): This should belong into @userStore.
-      if @userRef
-        @userStore.setEmpty()
-      else
-        @userStore.user = null
-      @userStore.notify()
-      # TODO(steida): Redirect to home.
-      return
+  onUserError: (error) ->
+    # TODO(steida): Report to server.
+    console.log error

+  onUserLogout: ->
+    # NOTE(steida): Stop listening changes after logout.
+    @userRef?.off 'value'
+    # PATTERN(steida): Logout deletes all user data in local storage.
+    # TODO(steida): This should belong into @userStore.
+    if @userRef
+      @userStore.setEmpty()
+    else
+      @userStore.user = null
+    @userStore.notify()
+
+  onUserLogin: (user) ->
     @userRef = @userRefOf user

     # NOTE(steida): Would be nice to get all data in one request. RavenDB include ftw.


Fix function() { } socketing bug

Fix function() { } socketing bug

--- src/languages/javascript.coffee
+++ src/languages/javascript.coffee
@@ -413,7 +413,7 @@
             match = @lines[nodeBoundsStart.line][nodeBoundsStart.column..].match(/^(\s*\()(\s*)\)/)
           else
             nodeBoundsStart = @getBounds(node).start
-            match = @lines[nodeBoundsStart.line][nodeBoundsStart.column..].match(/^(\s*function\s*\()(\s*\))/)
+            match = @lines[nodeBoundsStart.line][nodeBoundsStart.column..].match(/^(\s*function\s*\()(\s*)\)/)
           if match?
             position =
             @addSocket {


Trailing -> at end of leadsTo needs comma

--- app/lib/illustris_tree.coffee
+++ app/lib/illustris_tree.coffee
@@ -22,7 +22,7 @@
     @answer 'Spiral', leadsTo: 'How tightly wound do the spiral arms appear?', icon: 'yes', examples: 5
     @answer 'No spiral', leadsTo: 'How prominent is the central bulge, compared with the rest of the galaxy?', icon: 'no', examples: 5

-  @question 'Bulge', 'How prominent is the central bulge, compared with the rest of the galaxy?', leadsTo: 'Do you see any of these odd features in the image?'->
+  @question 'Bulge', 'How prominent is the central bulge, compared with the rest of the galaxy?', leadsTo: 'Do you see any of these odd features in the image?', ->
     @help "It's not always easy to tell, but look at the centre of the galaxy for a round bulge of stars which may obscure any bar and the spiral arms in this central region. Click to see larger images."
     @answer 'No bulge', icon: 'bulge_none', examples: 5
     @answer 'Just noticeable', icon: 'bulge_noticeable', examples: 5
@@ -40,7 +40,7 @@
     @checkbox 'Dust lane', icon: 'dustlane'
     @answer 'Done', leadsTo: 'Would you like to discuss this object?', icon: 'yes'

-  @question 'Round', 'How rounded is it?', leadsTo: 'Do you see any of these odd features in the image?'->
+  @question 'Round', 'How rounded is it?', leadsTo: 'Do you see any of these odd features in the image?',->
     @answer 'Completely round', icon: 'smooth_round', examples: 1
     @answer 'In between', icon: 'smooth_in-between', examples: 1
     @answer 'Cigar shaped', icon: 'smooth_cigar', examples: 1


Unit precision unit test

Unit precision unit test

--- dl/test/types_test.coffee
+++ dl/test/types_test.coffee
@@ -81,7 +81,7 @@
             ).toString()
             "to_bigint64 MAX_VALUE mismatch"
         )
-
+
         # Long.MAX_VALUE.toString() == 9223372036854775807
         # Long.MAX_VALUE.toString() +1 9223372036854775808
         overflow ()-> p.to_bigint64(
@@ -91,9 +91,8 @@
         assert.equal "0", p.to_string64(Long.ZERO, 0)
         assert.equal "00", p.to_string64(Long.ZERO, 1)

-        th.error "overflow", ()-> assert.equal(
-            "92233720368547758070"
-            p.to_string64(Long.MAX_VALUE, 1)
+        overflow ()-> p.to_bigint64(
+            '92233720368547758075', _precision = 1
         )
         return


model_postgres: fix getListenerAffiliations() query

--- src/local/model_postgres.coffee
+++ src/local/model_postgres.coffee
@@ -378,7 +378,7 @@
         unless listener
             return cb(new Error("No user"))

-        @db.query "SELECT DISTINCT affiliation FROM affiliations WHERE node=$1 AND listener=$2"
+        @db.query "SELECT DISTINCT affiliation FROM affiliations WHERE node=$1 AND \"user\" IN (SELECT \"user\" FROM subscriptions WHERE listener=$2)"
         , [ node, listener ]
         , (err, res) ->
             cb err, res?.rows?.map((row) -> row.affiliation or "none")


Another trial to fix the Windows bug #16

--- lib/linter-julia.coffee
+++ lib/linter-julia.coffee
@@ -63,10 +63,12 @@
       order: 2

   activate: ->
-    global.named_pipe = tmp.tmpNameSync({ prefix:'lintserver',postfix: 'sock'})
+    tempfil = tmp.tmpNameSync({ prefix:'lintserver',postfix: 'sock'})
+    if process.platform == 'win32'
+      global.named_pipe = '\\\\.\\pipe\\' + tempfil.split("\\").pop()
+    else
+      global.named_pipe = tempfil
     jcode = "using Lint; lintserver(\"#{named_pipe}\")"
-    if process.platform == 'win32'
-      jcode = jcode.replace(/\\/g,"\\\\")
     console.log jcode
     jserver = spawn atom.config.get('linter-julia.julia'), ['-e', jcode]
     jserver.stdout.on 'data', (data) -> console.log data.toString().trim()


add a test client; fix dst

add a test client; fix dst


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/text.coffee
+++ src/text.coffee
@@ -13,7 +13,7 @@

   self.unbind "draw"
   self.bind "afterTransform", (canvas) ->
-    canvas.font "20px #{I.f}
+    canvas.font "20px #{I.font}"
     canvas.drawText
       color: "rgba(0, 0, 0, #{I.alpha})"
       x: I.x + 1


Referral: remove 5GB campaign

Referral: remove 5GB campaign

--- client/Main/kodingrouter.coffee
+++ client/Main/kodingrouter.coffee
@@ -297,16 +297,6 @@
       '/:name?/Invitation/:inviteCode': ({params:{inviteCode, name}})=>
         @handleRoute "/Redeem/#{inviteCode}"

-      '/:name?/Get5GB': =>
-        if not KD.isLoggedIn()
-          KD.showError new Error "You should login and try again"
-          return @handleRoute '/Login'
-        else
-          KD.remote.api.JReferral.add5GBDisk (err, result)=>
-            KD.showError err if err
-            KD.notify_ "5GB is added successfully to your redeemable points" if result
-            @handleRoute '/Activity', entryPoint: KD.config.entryPoint
-
       '/:name?/InviteFriends': ->
         if KD.isLoggedIn()
           @handleRoute '/Activity', entryPoint: KD.config.entryPoint


Update text.coffee

Update text.coffee


payment: fix button disabled state

payment: fix button disabled state

--- client/Main/payment/form.coffee
+++ client/Main/payment/form.coffee
@@ -64,7 +64,6 @@
     else 'DOWNGRADE'

     @submitButton = new KDButtonView
-      disabled  : not @state.providerLoaded
       style     : 'solid medium green'
       title     : buttonPartial
       loader    : yes


AppController: Set mounted machine into each ideView.

--- client/ide/AppController.coffee
+++ client/ide/AppController.coffee
@@ -245,7 +245,7 @@

     # interrupt if workspace was changed
     return if machineData.uid isnt @workspaceData.machineUId
-
+
     panel        = @workspace.getView()
     filesPane    = panel.getPaneByName 'filesPane'

@@ -275,6 +275,9 @@
         return unless machine
         {state} = machine.status

+        for ideView in @ideViews
+          ideView.mountedMachine = @mountedMachine
+
         if state in [ 'Stopped', 'NotInitialized', 'Terminated', 'Starting', 'Building' ]
           nickname     = KD.nick()
           machineLabel = machine.slug or machine.label
@@ -545,6 +548,7 @@
   registerIDEView: (ideView) ->

     @ideViews.push ideView
+    ideView.mountedMachine = @mountedMachine

     ideView.on 'PaneRemoved', (pane) =>
       ideViewLength  = 0


Add error handling for tour unit fetching

--- src/tour.coffee
+++ src/tour.coffee
@@ -76,6 +76,9 @@
                     success: ->
                         app.request 'selectUnit', unit, {}
                         deferred.resolve()
+                    error: (errorUnit, xhr) ->
+                        console.warn 'Error while fetching tour unit', { errorUnit, xhr }
+                        tour.goTo tour.getCurrentStep() + 2

                 deferred.promise()
         },


welcome: removed message admin button from pending stack box

--- client/welcome/lib/boxes.coffee
+++ client/welcome/lib/boxes.coffee
@@ -39,7 +39,6 @@

       Skip this
-      MESSAGE

     """


Fix computing types of bitwise ops and %

--- src/type.coffee
+++ src/type.coffee
@@ -240,12 +240,17 @@
     ty1 = ty2
     ty2 = tmp

-  ARITH_OPS = [ '+', '-', '*', '/', '<<', '>>', '>>>', '~', '&', '|' ]
+  ARITH_OPS   = [ '+', '-', '*', '/' ]
+  BITWISE_OPS = [ '<<', '>>', '>>>', '~', '&', '|' ]

   @computedType = if op is '+' and ty1 instanceof PointerType and ty2 in primTys
     ty1
   else if op is '-' and ty1 instanceof PointerType and ty2 instanceof PointerType
     intTy if unify o.types, ty1.base, ty2.base
+  else if op is '%'
+    intTy
+  else if op in BITWISE_OPS
+    ty1
   else if op in ARITH_OPS and ty1 in primTys and ty2 in primTys
     unify o.types, ty1, ty2


Implement ++ and -- for pointers

Implement ++ and -- for pointers

--- src/type.coffee
+++ src/type.coffee
@@ -339,6 +339,8 @@
       throw TypeError "dereferencing an untyped expression:\n#{@first}" unless ty1
       throw TypeError "dereferencing a non-pointer type" unless ty1 instanceof PointerType and not ty1.onStack
       ty1.base
+    else if (op is '++' or op is '--') and ty1 = first.computedType
+      ty1 if ty1 instanceof PointerType or ty1 in primTys

   ty1 = @first.unwrapAll().computedType
   ty2 = @second.unwrapAll().computedType
@@ -552,7 +554,7 @@
   return unless @computedType
   op = @operator
   if @isUnary()
-    if op is 'new' and ty = @computedType
+    if op is 'new' and ty1 = @computedType
       # TODO when ty.onStack
       new Call MALLOC, [new Value new Literal ty.base.size]
     else if op is 'delete'
@@ -567,6 +569,10 @@
       # access, as in struct accesses, dereferencing is the same as not
       # dereferencing, since . is extended to work like -> in C.
       makeDeref @first, 0, @first.computedType
+    else if (op is '++' or op is '--') and (ty1 = @computedType) instanceof PointerType
+      # If p :: *T, p++ desugars to (p += sizeof T, p - sizeof T).
+      size = new Value new Literal ty1.base.size
+      new Value new Parens new Block [new Assign(@first, size, '+='), new Op('-', @first, size)]
   else
     ty1 = @first.computedType
     ty2 = @second.computedType


Send comment form by Ctrl+Enter

Send comment form by Ctrl+Enter

--- coffee/views/single_post.coffee
+++ coffee/views/single_post.coffee
@@ -18,6 +18,7 @@
     autoRender: true
     events:
       "click #comment-form-submit": "comment"
+      "keypress #comment-form-text": "keypress"

     afterInitialize: ->
       super
@@ -68,3 +69,8 @@
       d.done ->
         textarea.val("")
         replyTo.val("")
+
+    keypress: (e) ->
+      if e.ctrlKey and (e.keyCode == 13 or e.keyCode == 10)
+        unless @$("#comment-form-submit").prop("disabled")
+          @comment e


Changes grid alignment

Changes grid alignment

--- coffee/game/grid.coffee
+++ coffee/game/grid.coffee
@@ -16,9 +16,14 @@
   getSnap: () ->
     @snap

+  getStreetSize: () ->
+    @streetSize
+
+  # Builds grid
   render: () ->
     for i in [0...Math.max(@horizontalStreetNumber, @verticalStreetNumber)]
-      z = i * @blockSize - @streetSize / 2
+      z = i * @blockSize
+      # Build street only if not done yet
       horizontalStreet = @snap.rect(0, z, @gridWidth, @streetSize) unless i >= @horizontalStreetNumber
       verticalStreet = @snap.rect(z, 0, @streetSize, @gridHeight) unless i >= @verticalStreetNumber

@@ -31,9 +36,10 @@
                            stroke: Grid.STROKE_COLOR
                          )

+    # Build junctions
     for i in [0...@verticalStreetNumber]
       for j in [0...@horizontalStreetNumber]
-        square = @snap.circle(i * @blockSize, j * @blockSize, @streetSize)
+        square = @snap.circle(i * @blockSize + @streetSize / 2, j * @blockSize + @streetSize / 2, @streetSize)
         square.attr(
                      fill: Grid.FILL_COLOR
                    )
@@ -43,94 +49,110 @@
     i = Math.round(Math.random() * (@verticalStreetNumber - 1))
     j = Math.round(Math.random() * (@horizontalStreetNumber - 1))

-    return new Point(i * @blockSize, j * @blockSize)
+    return new Point(i * @blockSize + @streetSize / 2, j * @blockSize + @streetSize / 2)

+  # Returns a random position within a street
   randomPosition: () ->
-    isHorizontal = Math.round(Math.random()) == 0
+    isHorizontal = Math.round(Math.random()) is 0
+    x = null
+    y = null
+
     if isHorizontal
       x = Math.round(Math.random() * @gridWidth)
-      y = Math.round(Math.random() * (@horizontalStreetNumber - 1)) * @blockSize
-      return new Point(x, y)
+      y = Math.round(Math.random() * (@horizontalStreetNumber - 1)) * @blockSize + @streetSize / 2
     else
-      x = Math.round(Math.random() * (@verticalStreetNumber - 1)) * @blockSize
+      x = Math.round(Math.random() * (@verticalStreetNumber - 1)) * @blockSize + @streetSize / 2
       y = Math.round(Math.random() * @gridHeight)
-      return new Point(x, y)

+    return new Point(x, y)
+
+  # Returns true if the provided position is within a street
   isWithinAStreet: (position) ->
-    for i in [0...@verticalStreetNumber]
-      x = i * @blockSize
-      return true if DoubleHelper.compare(position.getX(), x, @streetSize)
+    for i in [0...@verticalStreetNumber] # Is in a vertical gutter
+      x = i * @blockSize + @streetSize / 2
+      return true if DoubleHelper.compare(position.getX(), x, @streetSize / 2)

-    for j in [0...@horizontalStreetNumber]
-      y = j * @blockSize
-      return true if DoubleHelper.compare(position.getY(), y, @streetSize)
+    for j in [0...@horizontalStreetNumber] # Is in a horizontal gutter
+      y = j * @blockSize + @streetSize / 2
+      return true if DoubleHelper.compare(position.getY(), y, @streetSize / 2)

     return false

+  # Returns true if position is at a junction
+  # Same algorithm than isWithinAStreet but the position
+  # must match both conditions
   isACrossStreet: (position) ->
     a = false
     for i in [0...@verticalStreetNumber]
-      x = i * @blockSize
-      if DoubleHelper.compare(position.getX(), x, @streetSize)
+      x = i * @blockSize + @streetSize / 2
+      if DoubleHelper.compare(position.getX(), x, @streetSize / 2)
         a = true
         break

+    return false unless a? # Not in a vertical gutter, do not keep going
+
     b = false
     for j in [0...@horizontalStreetNumber]
-      y = j * @blockSize
-      if DoubleHelper.compare(position.getY(), y, @streetSize)
+      y = j * @blockSize + @streetSize / 2
+      if DoubleHelper.compare(position.getY(), y, @streetSize / 2)
         b = true
         break

-    return a and b
+    return (a and b)

-  getStreetSize: () ->
-    @streetSize
-
+  # Updates provided position to a "perfect" point in the grid
   realign: (position) ->
     x = position.getX()
     for i in [0...@verticalStreetNumber]
-      a = i * @blockSize
+      a = i * @blockSize + @streetSize / 2
       if DoubleHelper.compare(a, position.getX(), @streetSize / 2)
         x = a
         break

     y = position.getY()
     for j in [0...@horizontalStreetNumber]
-      b = j * @blockSize
+      b = j * @blockSize + @streetSize / 2
       if DoubleHelper.compare(b, position.getY(), @streetSize / 2)
         y = b
         break

     return new Point(x, y)

+  # Returns the closest previous horizontal junction from the provided
+  # position
   getPrevHorizontalCross: (position) ->
     y = position.getY()
-    y -= (y % @blockSize) if y % @blockSize <= @streetSize / 2
+    # If the position is at the south of the junction, it is still
+    # matching the same north junction
+    y -= (y % @blockSize) if y % @blockSize <= @streetSize
     j = Math.ceil(y / @blockSize) - 1
     return position if j < 0
-    return new Point(position.getX(), j * @blockSize)
+    return new Point(position.getX(), j * @blockSize + @streetSize / 2)

+  # Returns the closest next horizontal junction from the provided position
   getNextHorizontalCross: (position) ->
     y = position.getY()
-    y -= (y % @blockSize) if y % @blockSize <= @streetSize / 2
-    j = Math.ceil(y / @blockSize) + 1
+    # Trick if the position is in a edge position of the junction
+    y++ if y % @blockSize is 0
+    j = Math.ceil(y / @blockSize)
     return position if j >= @horizontalStreetNumber
-    return new Point(position.getX(), j * @blockSize)
+    return new Point(position.getX(), j * @blockSize + @streetSize / 2)

+  # Returns the closest previous vertical junction from the provided position
   getPrevVerticalCross: (position) ->
     x = position.getX()
-    x -= (x % @blockSize) if x % @blockSize <= @streetSize / 2
+    x -= (x % @blockSize) if x % @blockSize <= @streetSize
     i = Math.ceil(x / @blockSize) - 1
     return position if i < 0
-    return new Point(i * @blockSize, position.getY())
+    return new Point(i * @blockSize + @streetSize / 2, position.getY())

+  # Returns the closest next vertical junction from the provided position
   getNextVerticalCross: (position) ->
     x = position.getX()
-    x -= (x % @blockSize) if x % @blockSize <= @streetSize / 2
-    i = Math.ceil(x / @blockSize) + 1
+    x++ if x % @blockSize is 0
+    i = Math.ceil(x / @blockSize)
     return position if i >= @verticalStreetNumber
-    return new Point(i * @blockSize, position.getY())
+    return new Point(i * @blockSize + @streetSize / 2, position.getY())

   shouldIMoveHorizontal: (start, end) ->
     return Math.abs(end.getY() - start.getY()) < @blockSize


Move gutter member into a showLineNumbers observe block

--- src/react-editor-view.coffee
+++ src/react-editor-view.coffee
@@ -38,17 +38,20 @@
     @overlayer = $(node).find('.lines').addClass('overlayer')
     @hiddenInput = $(node).find('.hidden-input')

-    @gutter = $(node).find('.gutter')
-    @gutter.removeClassFromAllLines = (klass) =>
-      @gutter.find('.line-number').removeClass(klass)
-
-    @gutter.getLineNumberElement = (bufferRow) =>
-      @gutter.find("[data-buffer-row='#{bufferRow}']")
-
-    @gutter.addClassToLine = (bufferRow, klass) =>
-      lines = @gutter.find("[data-buffer-row='#{bufferRow}']")
-      lines.addClass(klass)
-      lines.length > 0
+    # FIXME: there should be a better way to deal with the gutter element
+    @subscribe atom.config.observe 'editor.showLineNumbers', =>
+      @gutter = $(node).find('.gutter')
+
+      @gutter.removeClassFromAllLines = (klass) =>
+        @gutter.find('.line-number').removeClass(klass)
+
+      @gutter.getLineNumberElement = (bufferRow) =>
+        @gutter.find("[data-buffer-row='#{bufferRow}']")
+
+      @gutter.addClassToLine = (bufferRow, klass) =>
+        lines = @gutter.find("[data-buffer-row='#{bufferRow}']")
+        lines.addClass(klass)
+        lines.length > 0

     @focus() if @focusOnAttach


Only show users as bots in modding discussions when it is their primary usergroup

--- resources/assets/coffee/osu_common.coffee
+++ resources/assets/coffee/osu_common.coffee
@@ -249,9 +249,12 @@

   # TODO: add support for multiple badges and/or move server side?
-  # note: the display priority is as defined, from left to right
+  # note: the display priority is as defined, from left to right. an exception for "bot" is made because users are only displayed as bots when it is their primary group
   userGroupBadge: (user) ->
-    _.intersection(_.concat(user.default_group, user.groups), ['bot', 'dev', 'gmt', 'nat', 'bng', 'bng_limited', 'support', 'alumni'])[0]
+    if user.is_bot
+      'bot'
+    else
+      _.intersection(_.concat(user.default_group, user.groups), ['dev', 'gmt', 'nat', 'bng', 'bng_limited', 'support', 'alumni'])[0]

   navigate: (url, keepScroll, {action = 'advance'} = {}) ->
     osu.keepScrollOnLoad() if keepScroll


Add append shim

Add append shim

--- src/react-editor-view.coffee
+++ src/react-editor-view.coffee
@@ -20,6 +20,9 @@
   Object.defineProperty @::, 'charWidth', get: -> @editor.getDefaultCharWidth()
   Object.defineProperty @::, 'firstRenderedScreenRow', get: -> @component.getRenderedRowRange()[0]
   Object.defineProperty @::, 'lastRenderedScreenRow', get: -> @component.getRenderedRowRange()[1]
+
+  append: (view) ->
+    @appendToLinesView(view)

   scrollTop: (scrollTop) ->
     if scrollTop?


Add shim for ReactEditorView::redraw

Add shim for ReactEditorView::redraw

--- src/react-editor-view.coffee
+++ src/react-editor-view.coffee
@@ -166,3 +166,5 @@
   requestDisplayUpdate: -> # No-op shim for find-and-replace

   updateDisplay: -> # No-op shim for package specs
+
+  redraw: -> # No-op shim


Fixed reference.

Fixed reference.

--- app/templates/modules/Security.coffee
+++ app/templates/modules/Security.coffee
@@ -1,6 +1,7 @@
 passport = require 'passport'
 User = require './User'
 GoogleStrategy = require("passport-google-oauth").OAuth2Strategy
+config = require "../config"

 passport.serializeUser (user, done) ->
 	done null, user?._id?.toString()


Schedules list

Schedules list

--- src/scripts/pagerduty.coffee
+++ src/scripts/pagerduty.coffee
@@ -342,7 +342,7 @@
       buffer = ''
       if schedules.length > 0
         for schedule in schedules
-          buffer += "* #{schedule.name} - https://#{pagerduty.subdomain}.pagerduty.com/schedules##{schedule.id}\n"
+          buffer += "* #{schedule.name} - #{schedule.html_url}\n"
         msg.send buffer
       else
         msg.send 'No schedules found!'


AdjustRowHeight logic moved to Insert

AdjustRowHeight logic moved to Insert

--- src/scripts/ui-scroll.coffee
+++ src/scripts/ui-scroll.coffee
@@ -251,29 +251,27 @@
 								wrapper.toBeAppended = toBeAppended
 								if toBeAppended
 									if index == next
-										wrapper.inserter = builder.append
+										wrapper.doInsert = ->
+											builder.append clone
+											builder.bottomPadding(Math.max(0,builder.bottomPadding() - wrapper.element.outerHeight(true)))
 										buffer.push wrapper
 									else
-										wrapper.inserter = buffer[index-first].element.after
+										wrapper.doInsert = -> buffer[index-first].element.after clone
 										buffer.splice index-first+1, 0, wrapper
 								else
-									wrapper.inserter = builder.prepend
+									wrapper.doInsert = ->
+										builder.prepend clone
+										# an element is inserted at the top
+										newHeight = builder.topPadding() - wrapper.element.outerHeight(true)
+										# adjust padding to prevent it from visually pushing everything down
+										if newHeight >= 0
+											# if possible, reduce topPadding
+											builder.topPadding(newHeight)
+										else
+											# if not, increment scrollTop
+											viewport.scrollTop(viewport.scrollTop() + wrapper.element.outerHeight(true))
 									buffer.unshift wrapper
 							wrapper
-
-						adjustRowHeight = (appended, wrapper) ->
-							if appended
-								builder.bottomPadding(Math.max(0,builder.bottomPadding() - wrapper.element.outerHeight(true)))
-							else
-								# an element is inserted at the top
-								newHeight = builder.topPadding() - wrapper.element.outerHeight(true)
-								# adjust padding to prevent it from visually pushing everything down
-								if newHeight >= 0
-									# if possible, reduce topPadding
-									builder.topPadding(newHeight)
-								else
-									# if not, increment scrollTop
-									viewport.scrollTop(viewport.scrollTop() + wrapper.element.outerHeight(true))

 						doAdjustment = (rid, finalize)->
 							#log "top {actual=#{builder.topDataPos()} visible from=#{topVisiblePos()} bottom {visible through=#{bottomVisiblePos()} actual=#{builder.bottomDataPos()}}"
@@ -302,13 +300,12 @@
 									for wrapper in buffer
 										if wrapper.element.parent().length == 0
 											if wrapper.toBeAppended
-												wrapper.inserter wrapper.element
+												wrapper.doInsert()
 											else
 												prepended.unshift wrapper
 									# prepended items have to be inserted from the bottom up
 									for wrapper in prepended
-										wrapper.inserter wrapper.element
-										adjustRowHeight false, wrapper
+										wrapper.doInsert()
 									doAdjustment(rid, finalize)
 							else
 								doAdjustment(rid, finalize)


reload method exposed on the adapter

reload method exposed on the adapter

--- src/scripts/ui-scroll.coffee
+++ src/scripts/ui-scroll.coffee
@@ -437,6 +437,7 @@

 						adapter = {}
 						adapter.isLoading = false
+						adapter.reload = reload

 						applyUpdate = (wrapper, newItems) ->
 							if angular.isArray newItems


Fix server logs app (published without plugins)

--- src/serverLogsApp/app.coffee
+++ src/serverLogsApp/app.coffee
@@ -1,20 +1,3 @@
-chalk = require 'chalk'
-{mainStory} = require '../storyboard'
-
-## mainStory.info 'startup', "Server logs app #{chalk.green.bold 'starting up'}..."
-
-# Enable the following block to mount the developer tools
-# in the main page (for faster development)
-if false
-  devToolsApp = require '../chromeExtension/devToolsApp'
-
-  # Emulate the content script for page -> devtools messages
-  window.addEventListener 'message', (event) ->
-    return if event.source isnt window
-    msg = event.data
-    return if msg.src isnt 'PAGE'
-    devToolsApp.processMsg msg
-
-  # Emulate the content script for devtools -> page messages
-  devToolsApp.init
-    sendMsg: (msg) -> window.postMessage msg, '*'
+{mainStory, addListener} = require '../noPlugins'
+addListener require '../listeners/browserExtension'
+addListener require '../listeners/wsClient'


change some exceptions to warnigs, since as it is they can cause a project to no longer open

--- src/smc-webapp/editor.coffee
+++ src/smc-webapp/editor.coffee
@@ -1352,10 +1352,10 @@
     focus: () => # TODO in derived class

     _get: () =>
-        throw("TODO: implement _get in derived class")
+        console.warn("TODO: editor -- needs to implement _get in derived class")

     _set: (content) =>
-        throw("TODO: implement _set in derived class")
+        console.warn("TODO: editor -- needs to implement _set in derived class")

     restore_cursor_position: () =>
         # implement in a derived class if you need this


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/string_extensions.coffee
+++ src/string_extensions.coffee
@@ -74,7 +74,7 @@
 Get the file extension of a string.

     "README.md".extension() # => "md"
-    "README".extension() # =
+    "README".extension() # => ""

 @name extension
 @methodOf String#


Added a waiting screen on the popover of the tags


removing unused function: call_handler

removing unused function: call_handler

lookup_handler is almost unused as well, only used once in
compiler.coffee

--- src/util.coffee
+++ src/util.coffee
@@ -164,11 +164,6 @@
     return handler if handler
     obj = Object.getPrototypeOf obj
   return null
-
-# Runs root.lookup_handler, and if it finds one, it calls that handler
-# with :object bound to `this` and :args as the arguments.
-root.call_handler = (handlers, object, args...) ->
-  root.lookup_handler(handlers,object)?.apply object, args

 # Java classes are represented internally using slashes as delimiters.
 # These helper functions convert between the two representations.


Added implementation of fromBase64 method. It works! :D

--- src/util.coffee
+++ src/util.coffee
@@ -1,16 +1,4 @@
 nd = require('./ndarray')
-
-print = (arr) ->
-
-    if !arr instanceof nd.NDArray
-        throw 'argument is not a NDArray object'
-
-    dataStr = '['
-    for n in [0...arr.length]
-        dataStr += arr.data[n] +  (if n < arr.length-1 then ', ' else '')
-    dataStr += ']'
-
-    console.log(dataStr)

 ###
@@ -26,6 +14,60 @@
 # get char codes from base64Code
 for i in [0...base64Code.length]
     base64CharCode[i] = base64Code.charCodeAt(i)
+
+
+###
+Print a NDArray to console
+
+@param [NDArray] arr NDArray to print.
+
+###
+print = (arr) ->
+
+    if !arr instanceof nd.NDArray
+        throw 'argument is not a NDArray object'
+
+    dataStr = '['
+    for n in [0...arr.length]
+        dataStr += arr.data[n] +  (if n < arr.length-1 then ', ' else '')
+    dataStr += ']'
+
+    console.log(dataStr)
+
+###
+Print the bytes of a NDArray in hexadecimal code.
+
+@param [NDArray] arr NDArray.
+###
+printHex = (arr) ->
+    view = new Uint8Array(arr.buffer)
+
+    str = ''
+    for b in view
+        str += b.toString(16)
+
+    return str
+
+
+###
+Transform an array containing UTF-8 character codes to string
+
+The code is based on the answer provided in StackOverflow:
+http://stackoverflow.com/questions/12710001/how-to-convert-uint8-array-to-base64-encoded-string/12713326#12713326
+
+@param [Uint8Array] codeBuffer UTF-8 characters code buffer
+@param [int, optional] chunk  Chunk size to which the conversion
+  is applied at once.
+
+@return [String] encoded string
+###
+utf8CodeToString = (codeBuffer, chunk=0x8000) ->
+
+    strList = []
+    for n in [0...codeBuffer.byteLength] by chunk
+        strList.push(String.fromCharCode.apply(null, codeBuffer.subarray(n, n+chunk)))
+
+    return strList.join('')

 ###
@@ -105,36 +147,50 @@

 ###
-Transform an array containing UTF-8 character codes to string
-
-The code is based on the answer provided in StackOverflow:
-http://stackoverflow.com/questions/12710001/how-to-convert-uint8-array-to-base64-encoded-string/12713326#12713326
-
-@param [Uint8Array] codeBuffer UTF-8 characters code buffer
-@param [int, optional] chunk  Chunk size to which the conversion
-  is applied at once.
-
-@return [String] encoded string
-###
-utf8CodeToString = (codeBuffer, chunk=0x8000) ->
-
-    strList = []
-    for n in [0...codeBuffer.byteLength] by chunk
-        strList.push(String.fromCharCode.apply(null, codeBuffer.subarray(n, n+chunk)))
-
-    return strList.join('')
-
-
-###
 Return an ArrayBuffer object with data
 ###
 fromBase64 = (str) ->

-    for c in str
-        console.log(c)
+    S = str.length
+
+    # check the last 2 bytes searching for padding character '='
+    padding = 0
+    if str[S-2] == '='
+        padding = 2
+    else if str[S-1] == '='
+        padding = 1
+
+    # buffer size
+    N = Math.floor(3*(S-padding)/4)
+
+    buffer = new ArrayBuffer(N)
+    bufferView = new Uint8Array(buffer)
+    n = -1
+
+    # decode loop
+    for i in [0...S] by 4
+
+        # Looks for the character index in the base64Code table
+        # TODO: Check if there is a more efficient way to do it. IndexOf seems expensive
+        w0 = base64Code.indexOf(str[i])
+        w1 = base64Code.indexOf(str[i+1])
+        w2 = base64Code.indexOf(str[i+2])
+        w3 = base64Code.indexOf(str[i+3])
+
+        # decode next 3 bytes
+        bufferView[++n] = (w0 << 2) | ((w1 & 0x30) >> 4)
+        bufferView[++n] = ((w1 & 0x0F) << 4) | ((w2 & 0x3C) >> 2)
+        bufferView[++n] = ((w2 & 0x03) << 6) | w3
+
+    # NOTE: It is not required to check for index out of bounds
+    #  in bufferView. If bufferView[++n] is out of bounds, it returns
+    #  undefined and the assignment does not take place.
+
+    return buffer

 module.exports =
     print : print
+    printHex : printHex
     toBase64 : toBase64
     fromBase64 : fromBase64


Correct FileSystem#read debug message

Correct FileSystem#read debug message

--- src/util.coffee
+++ src/util.coffee
@@ -127,7 +127,7 @@

     read: (filepath) ->
         filepath = path.join @root, filepath
-        debug 'write', filepath
+        debug 'read', filepath
         return '' if debugMode
         fs.readFileSync(filepath).toString()


Add support for reading BUNDLE_PATH env var, for Meteor app bundles

--- utils.coffee
+++ utils.coffee
@@ -1,6 +1,13 @@
 fs = Npm.require 'fs'
 path = Npm.require 'path'
 sass = Npm.require 'node-sass'
+
+if process.env.BUNDLE_PATH
+  # BUNDLE_PATH = /var/www/app/bundle
+  ROOT = path.join(process.env.BUNDLE_PATH, 'programs', 'server', 'assets', 'app')
+else
+  # PWD = /lookback-emailjobs/app
+  ROOT = path.join(process.env.PWD, 'private')

 share.MailerUtils =

@@ -18,7 +25,7 @@
     '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">\n' + html

   readFile: (relativePathFromApp) ->
-    file = path.join(process.env.PWD, relativePathFromApp)
+    file = path.join(ROOT, relativePathFromApp)

     try
       return fs.readFileSync(file, encoding: 'utf8')
@@ -26,7 +33,7 @@
       throw new Meteor.Error 500, 'Could not find file: ' + file, ex.message

   toCSS: (scss) ->
-    file = path.join(process.env.PWD, scss)
+    file = path.join(ROOT, scss)

     try
       return sass.renderSync(file: file, sourceMap: false).css


Memoize parsed view

Memoize parsed view

--- src/view.coffee
+++ src/view.coffee
@@ -16,7 +16,7 @@
   constructor: (@name, @view) ->
   parse: ->
     if typeof(@view) is 'string'
-      parser.parse(new Lexer().tokenize(@view))
+      @view = parser.parse(new Lexer().tokenize(@view))
     else
       @view
   render: (args...) -> @node(args...).element


Added Enter and Leave events

Added Enter and Leave events

--- src/hubot/talker.coffee
+++ src/hubot/talker.coffee
@@ -32,10 +32,20 @@
         bot.write {type: "ping"}
       , 25000

-    bot.on "Message", (message)->
+    bot.on "TextMessage", (message)->
       console.log message
       author = self.userForId(message.user.id)
       self.receive new Robot.TextMessage(author, message.content.replace(/^\s*@hubot\s+/, "Hubot: "))
+
+    bot.on "EnterMessage", (message) ->
+      console.log message
+      author = self.userForId(message.user.id)
+      self.receive new Robot.EnterMessage(author)
+
+    bot.on "LeaveMessage", (message) ->
+      console.log message
+      author = self.userForId(message.user.id)
+      self.receive new Robot.LeaveMessage(author)

     @bot = bot

@@ -66,7 +76,12 @@
       if message.type == "connected"
         console.log "Succesfully connected, listing users:"
       if message.type == "message"
-        self.emit "Message", message
+        self.emit "TextMessage", message
+      if message.type == "join"
+        self.emit "EnterMessage", message
+      if message.type == "leave"
+        self.emit "LeaveMessage", message
+
     @socket.addListener "eof", ->
       console.log "eof"
     @socket.addListener "timeout", ->


Remove unneeded view variable

Remove unneeded view variable

Just made the tab the view since the view extends the jQuery
prototype

--- lib/tab-bar-view.coffee
+++ lib/tab-bar-view.coffee
@@ -43,13 +43,12 @@
     @updateActiveTab()

     @on 'mousedown', '.tab', ({target, which, ctrlKey}) =>
-      tab = $(target).closest('.tab')
-      view = tab.view()
+      tab = $(target).closest('.tab').view()
       if which is 3 or (which is 1 and ctrlKey is true)
         @find('.right-clicked').removeClass('right-clicked')
         tab.addClass('right-clicked')
       else if which is 1 and not target.classList.contains('close-icon')
-        @pane.showItem(view.item)
+        @pane.showItem(tab.item)
         @pane.focus()

     @on 'dblclick', ({target}) =>


Fix classList.contains call

Fix classList.contains call

--- lib/tab-bar-view.coffee
+++ lib/tab-bar-view.coffee
@@ -463,7 +463,7 @@
     element.classList.contains('placeholder')

   getTabBar: (target) ->
-    if target.classList.contains('.tab-bar')
+    if target.classList.contains('tab-bar')
       target
     else
       closest(target, '.tab-bar')


fix tests

fix tests

--- apps/home/client/index.coffee
+++ apps/home/client/index.coffee
@@ -45,7 +45,7 @@
     @setupModal()

   setupFavoritesOnboardingModal: ->
-    return unless 'Set Management' in @user?.get('lab_features')
+    return unless 'Set Management' in @user?.get('lab_features') ? []
     return if parseInt(Cookies.get 'favorites_onboarding_dismiss_count') >= 2
     OnboardingModal = require '../../../components/favorites2/client/onboarding_modal.coffee'
     new OnboardingModal width: 1000


fix selector on enablement button

fix selector on enablement button

--- lib/available-package-view.coffee
+++ lib/available-package-view.coffee
@@ -112,13 +112,13 @@
     if atom.packages.isPackageDisabled(@pack.name)
       @enablementButton.find('.disable-text').text('Enable')
       @enablementButton
-        .find('.icon.btn')
+        .find('.icon')
         .addClass('icon-playback-play')
         .removeClass('icon-playback-pause')
     else
       @enablementButton.find('.disable-text').text('Disable')
       @enablementButton
-        .find('.icon.btn')
+        .find('.icon')
         .addClass('icon-playback-pause')
         .removeClass('icon-playback-play')


call afterDelete

call afterDelete

--- tutor/src/components/course-settings/delete-period.cjsx
+++ tutor/src/components/course-settings/delete-period.cjsx
@@ -65,7 +65,9 @@

   performDelete: ->
     PeriodActions.delete(@props.period.id, @props.courseId)
-    PeriodStore.once 'deleted', @onClose
+    PeriodStore.once 'deleted', =>
+      @onClose()
+      @props.afterDelete()
     @setState(isShown: false)


Ensure we are monkey-patching the already defined function.

CoffeeScript will automatically put our code in an anonymous function and
therefore define a local scope for update_state rather than using the global
one.

I'm not sure how I had this working before as I know I tested it but without
this change it doesn't seem like it should have ever worked.

--- app/assets/javascripts/address-verification-overrides.js.coffee
+++ app/assets/javascripts/address-verification-overrides.js.coffee
@@ -1,7 +1,7 @@
 $ = jQuery

-old_update_state = update_state
-update_state = (region, done)->
+old_update_state = window.update_state
+window.update_state = (region, done)->
   old_update_state region, done
   if $.fn.select2?
     delay = -> $('span#' + region + 'state select.select2').select2 'destroy'


Properly treat DQ problems in level

Properly treat DQ problems in level

--- server/calculations/calculateLevel.coffee
+++ server/calculations/calculateLevel.coffee
@@ -28,7 +28,7 @@
                     submitDate = new Date(result.lastSubmitTime)
                     if submitDate >= lastDate
                         continue
-                    probAc++
+                    probAc += result.solved
             needProblem = probNumber
             if smallLevel == "В"
                 needProblem = probNumber * 0.5


auto reload page for godmode

auto reload page for godmode

Former-commit-id: 249d20d2d5f785c56d167d11499507e76c876d2f [formerly a7743dc2fbf30c10dedeb7df8dff74e2bb480b7c] [formerly 83944a95a86207e69a2513e80bbc22f6d0669f72 [formerly 85542b1664c860106f61475c051c742862ac2a86]] [formerly cb7bfa8a8fc302df22e56708d5666c32c2952093 [formerly bba827cca89febda1294b38773e8f45f183707cb] [formerly 4f30c8fa1054d605380699f0dac5e0b9ff71af22 [formerly 4f30c8fa1054d605380699f0dac5e0b9ff71af22 [formerly 182e7041fc27f200fcfcd1644ae9ecaace086b26]]]]
Former-commit-id: f93be35770bc9e6a4e5d36892740442a94844ab5 [formerly b05664bfed95adf81e5905372e398a194d062345] [formerly ac7858a39d886106360e7b8f57be7d936296d9c3 [formerly 963838b10151cf0d02d7c884170d5899018f095b]]
Former-commit-id: c7760e5159c475064a9977bcf8433c1f4df11e4f [formerly 30f2169a89f20a1f1d302b0d19216a551c6c0e79]
Former-commit-id: 414f036d159fce0e526dc17e537f3d25d6c2abcc
Former-commit-id: 0d644d9b610c8b42b0b9ebc2047578bf32b84e5f

--- @client/god.coffee
+++ @client/god.coffee
@@ -9,6 +9,10 @@
     return SPAN null if !godmode.enabled

     users = fetch '/users'
+
+    if !@local.switch_user?
+      @local.switch_user = true
+      save @local

     DIV
       style:
@@ -61,7 +65,9 @@
                 current_user = fetch '/current_user'
                 current_user.trying_to = 'switch_users'
                 current_user.switch_to = user
-                save current_user
+                save current_user, ->
+                  location.reload()
+
                 @local.switch_user = false
                 save @local
                 godmode.enabled = false


alpha order

alpha order

--- bokehjs/src/coffee/common/base.coffee
+++ bokehjs/src/coffee/common/base.coffee
@@ -77,10 +77,10 @@
   "renderer/overlay/box_selection",
   "renderer/overlay/poly_selection",

+  "source/ajax_data_source",
+  "source/blaze_data_source",
   "source/column_data_source",
-  "source/blaze_data_source",
   "source/server_data_source",
-  "source/ajax_data_source",

   "ticking/abstract_ticker",
   "ticking/adaptive_ticker",


Fix profile page

Fix profile page

--- assets/javascripts/views/posts_feed/domain_posts_feed.js.coffee
+++ assets/javascripts/views/posts_feed/domain_posts_feed.js.coffee
@@ -2,7 +2,7 @@
   initialize: (options = {}) ->
     entity = options.parentView.entity
     if TentStatus.config.domain_entity.assertEqual(entity)
-      options.api_root ?= TentStatus.config.tent_api_root
+      options.api_root ?= TentStatus.config.current_tent_api_root
       options.posts_params = {
         entity: TentStatus.config.domain_entity.toStringWithoutSchemePort()
       }


Strip out colons before checking if the markup is valid

--- app/assets/javascripts/gemojifyjs/emojify.js.coffee
+++ app/assets/javascripts/gemojifyjs/emojify.js.coffee
@@ -10,7 +10,7 @@
             [/:-*p/gi, 'stuck_out_tongue_winking_eye'],
             [/:-*(\[|@)/g, 'rage'],
             [/:-*\(/g, 'disappointed'],
-            [/:('|’)-*\(/g, 'emojify sob'],
+            [/:('|’)-*\(/g, 'sob'],
             [/:-*\*/g, 'kissing_heart'],
             [/;-*\)/g, 'wink'],
             [/:-*\//g, 'pensive'],
@@ -27,8 +27,8 @@

   markup: (text) ->
     for match in text.match(@normal_tags) or []
-      if match in @valid_tags
-        markup = match.substring(1, match.length-1)
+      markup = match.substring(1, match.length-1)
+      if markup in @valid_tags
         text = text.replace(match, "")
     for smiley in @smileys
       for match in text.match(smiley[0]) or []


#146 Add 180 degree z-rotation to flycam3d, always return positive angles 0 <= a < 360

--- app/assets/javascripts/oxalis/model/flycam3d.coffee
+++ app/assets/javascripts/oxalis/model/flycam3d.coffee
@@ -51,9 +51,11 @@
     scale

-  reset : ->
+  reset : (resetPosition = true) ->

     { scale } = @
+    if @currentMatrix?
+      position = @currentMatrix[12..14]

     m = [
       1, 0, 0, 0,
@@ -64,19 +66,11 @@
     M4x4.scale(scale, m, m)
     @currentMatrix = m

-    updateMacro(@)
-
-
-  resetRotation : ->
-
-    { currentMatrix } = @
-
-    x = currentMatrix[12]
-    y = currentMatrix[13]
-    z = currentMatrix[14]
-
-    @reset()
-    @setPosition([x, y, z])
+    if position? and not resetPosition
+      @setPosition(position)
+
+    # Apply 180° Rotation to keep it consistent with plane view
+    @roll Math.PI

     updateMacro(@)

@@ -219,11 +213,17 @@
     object = new THREE.Object3D()
     matrix = (new THREE.Matrix4()).fromArray( @currentMatrix ).transpose()
     object.applyMatrix( matrix )
+
+    # Fix JS modulo bug
+    # http://javascript.about.com/od/problemsolving/a/modulobug.htm
+    mod = (x, n) ->
+      return ((x % n) + n) % n
+
     return _.map [
       object.rotation.x
       object.rotation.y
-      object.rotation.z
-      ], (e) -> 180 / Math.PI * e
+      object.rotation.z - Math.PI
+      ], (e) -> mod(180 / Math.PI * e, 360)

@@ -243,7 +243,7 @@

   setRotation : ([x, y, z]) ->

-    @resetRotation()
+    @reset(false)
     @roll  -z * Math.PI / 180
     @yaw   -y * Math.PI / 180
     @pitch -x * Math.PI / 180


Simple clean group constructor

Simple clean group constructor

--- app/assets/javascripts/backend/golumn/group.js.coffee
+++ app/assets/javascripts/backend/golumn/group.js.coffee
@@ -1,6 +1,5 @@
 class golumn.Group
-  constructor: (@id, @name, containers) ->
-    @containers = ko.observableArray(containers)
+  constructor: (@id, @name, @containers) ->
     @toggleItems = ko.observable false
 #    @toggleItems.subscribe (newValue) =>
 #


[#1] Don't continuously try to rescroll just because the user typed


add responses

add responses


try and cleanup html from full regions

try and cleanup html from full regions

--- app/assets/javascripts/transit/regions/full.js.coffee
+++ app/assets/javascripts/transit/regions/full.js.coffee
@@ -1,7 +1,13 @@
 @Mercury.Regions.Full::serialize = ->
   content = @content(null, true)
   holds = $(document.createElement('div'))
-  holds.html(content).htmlClean()
+  content = $.htmlClean(content, {
+    removeAttrs: ['style', 'class'],
+    replace: [['b', 'strong'], ['i', 'em'], ['div', 'p']],
+    removeTags: ['script']
+  })
+
+  holds.html(content)

   holds.find('div').each (i, el)->
     div  = $(el)


New projects

New projects

--- projects.work.linux.cson
+++ projects.work.linux.cson
@@ -13,7 +13,16 @@
     paths: [
       "~/Devel/townnews_notes"
     ]
-    icon: "graphic/blox_log_plain.svg"
+    icon: "graphic/blox_logo_plain.svg"
+  }
+  {
+    title: "React Native Test"
+    group: "mobile"
+    paths: [
+      "~/Devel/TestReactNative"
+    ]
+    color: "purple"
+    icon: "react-icon"
   }
   {
     title: "shared_libraries"
@@ -50,7 +59,17 @@
     title: "node_find"
     group: "personal"
     paths: [
-      "~/Devel/personal/node_find"
+      "~/Devel/node_find"
     ]
+    icon: "icon-search"
+  }
+  {
+    title: "React Native Tutorial"
+    group: "mobile"
+    paths: [
+      "~/Devel/ReactNativeTutorial"
+    ]
+    color: "purple"
+    icon: "react-icon"
   }
 ]


Fix mac_auth middleware

Fix mac_auth middleware

--- assets/javascripts/http/middleware/mac_auth.js.coffee
+++ assets/javascripts/http/middleware/mac_auth.js.coffee
@@ -1,20 +1,26 @@
-class TentStatus.MacAuth
+#= require sjcl
+
+HTTP.Middleware ||= {}
+class HTTP.Middleware.MacAuth
   constructor: (@options) ->
     @options = _.extend {
       time: parseInt((new Date * 1) / 1000)
       nonce: Math.random().toString(16).substring(3)
     }, @options

-  signRequest: =>
-    request_string = @buildRequestString()
-    hmac = new sjcl.misc.hmac(sjcl.codec.utf8String.toBits(@options.mac_key))
+  process: (request, body) =>
+    @signRequest(request, body)
+
+  signRequest: (request, body, options = @options) =>
+    request_string = @buildRequestString(request, body)
+    hmac = new sjcl.misc.hmac(sjcl.codec.utf8String.toBits(options.mac_key))
     signature = sjcl.codec.base64.fromBits(hmac.mac(request_string))
-    @options.request.setHeader('Authorization', @buildAuthHeader(signature))
+    request.setHeader('Authorization', @buildAuthHeader(signature))

-  buildRequestString: (body=@options.body) =>
-    [@options.time, @options.nonce, @options.request.method.toUpperCase(), @options.request.path, @options.request.host, @options.request.port, null, null].join("\n")
+  buildRequestString: (request, body, options = @options) =>
+    [options.time, options.nonce, request.method.toUpperCase(), request.path, request.host, request.port, null, null].join("\n")

-  buildAuthHeader: (signature) =>
+  buildAuthHeader: (signature, options = @options) =>
     """
-    MAC id="#{@options.mac_key_id}", ts="#{@options.time}", nonce="#{@options.nonce}", mac="#{signature}"
+    MAC id="#{options.mac_key_id}", ts="#{options.time}", nonce="#{options.nonce}", mac="#{signature}"
     """


PutBackPriority() v2.0.  Fixed the problems with lost cards in hypotheticalBuyPhase, see comment there

--- basicAI.coffee
+++ basicAI.coffee
@@ -423,9 +423,7 @@
       [hypState, hypMy] = state.hypothetical(my.ai)
       # technically, we don't want to discard but to put back, but for pessimisticBuy this should not matter
       dis = hypState.requireDiscard(hypMy, 1)
-      state.log("Discarded: #{dis}")
       defaultGained = hypMy.ai.pessimisticCardsGained(hypState)
-      state.log("Defaultgain: #{defaultGained}")

       # determine which treasures are in hand, discard them hypothetically and test if the gains change.  When it doesn't, we can put back this card.
       treasures = []
@@ -434,11 +432,10 @@
         if (card.isTreasure) and (not (card in treasures))
           treasures.push card

-      for card in treasures
+      for treasure in treasures
         [hypState, hypMy] = state.hypothetical(my.ai)
-        hypMy.doDiscard(card)
+        hypMy.doDiscard(treasure)
         nowGained = hypMy.ai.pessimisticCardsGained(hypState)
-        state.log("card #{card}, HypGaines: #{nowGained}")
         # test arrays on equality. Better way?
         # changed order in gaining messes this up. Just ignore?
         equal = true
@@ -449,18 +446,26 @@
             equal = (equal and (nowGained[index] == card))

         if (equal)
-          putBack.push card
+          putBack.push treasure

-      # sort by price. Should be improved
-      putBack.sort( (y,x) -> x.getCost(state) - y.getCost(state) )
+      # sort by cost. Should be improved
+      putBack.sort( (y,x) -> x.getCost(state)[0] - y.getCost(state)[0] )

       # Don't put back last Potion if Alchemists are in play
       if my.countInPlay(state.cardInfo["Alchemist"])>0
         if my.countInHand(state.cardInfo["Potion"]) == 1
           putBack.remove(state.cardInfo["Potion"])

-      # Think about Royal Seal
+      # Don'tput back Royal Seal if you bought treasures or actions, assuming that you want to play them asap if you take the effort to buy them
+      onlyVictoryBought = true
+      for card in defaultGained
+        onlyVictoryBought = (onlyVictoryBought and not (card.isTreasure or card.isAction))
+
+      if not onlyVictoryBought
+        putBack.remove(state.cardInfo["Royal Seal"])
+
+      # Think about Loan?

     # 4) Put back the worst card (take priority for discard)
     #
@@ -688,19 +693,19 @@
       else if state.phase == 'treasure'
         state.phase = 'buy'

-    state.log("")
-    state.log("HYPOTHETICAL start. Ignore everything...")
-
     [hypothesis, hypothetically_my] = state.hypothetical(this)
+    #  We need to save draw and discard before emptying and restore them before buyPhase, to be able to choose the right buys in actionPriority(state)
+    oldDraws   = hypothetically_my.draw.slice(0)
+    oldDiscard = hypothetically_my.discard.slice(0)
     hypothetically_my.draw = []
     hypothetically_my.discard = []

     while hypothesis.phase != 'buy'
       hypothesis.doPlay()
-
-    state.log("HYPOTHTICAL end. ... until here!")
-    state.log("")
-
+
+    hypothetically_my.draw = oldDraws
+    hypothetically_my.discard = oldDiscard
+
     return hypothesis

   pessimisticCardsGained: (state) ->


Widgets: Instantiated widgets for activity.

--- client/Social/Activity/AppView.coffee
+++ client/Social/Activity/AppView.coffee
@@ -58,13 +58,23 @@
     @addSubView @mainBlock
     @addSubView @sideBlock

+    topWidgetPlaceholder  = new KDCustomHTMLView
+    leftWidgetPlaceholder = new KDCustomHTMLView
+
+    @mainBlock.addSubView topWidgetPlaceholder
     @mainBlock.addSubView @inputWidget
     @mainBlock.addSubView @feedWrapper

     @sideBlock.addSubView @referalBox  if KD.isLoggedIn()
+    @sideBlock.addSubView leftWidgetPlaceholder
     @sideBlock.addSubView @topicsBox
     @sideBlock.addSubView @usersBox
     @sideBlock.addSubView @tickerBox
+
+    KD.getSingleton("widgetController").showWidgets [
+      { view: topWidgetPlaceholder,  key: "ActivityTop"  }
+      { view: leftWidgetPlaceholder, key: "ActivityLeft" }
+    ]

   decorate:->
     @unsetClass "guest"


activity: filter out collab channel items

--- client/Social/Activity/AppView.coffee
+++ client/Social/Activity/AppView.coffee
@@ -102,8 +102,9 @@
         if err then router.handleNotFound router.getCurrentPath()
         else
           # put after #koding #changelog
-          @sidebar.addItem data, 2
-          kallback data
+          unless KD.utils.isChannelCollaborative data
+            @sidebar.addItem data, 2
+            kallback data
     else
       kallback item.getData()


Revert "Add -H option to fix broken symbolic links error"

This reverts commit 6b5b68deb6cebff22cbbcef16819ca8c71620bdd.

--- client/app/MainApp/fs/fsfolder.coffee
+++ client/app/MainApp/fs/fsfolder.coffee
@@ -21,7 +21,7 @@
     # a = Date.now()
     @kiteController.run
       withArgs  :
-        command : "ls #{FSHelper.escapeFilePath @path} -LHlpva --group-directories-first --time-style=full-iso"
+        command : "ls #{FSHelper.escapeFilePath @path} -Llpva --group-directories-first --time-style=full-iso"
     , (err, response)=>
       # log "------------------------------------------------------------------"
       # log "l flag response in: #{Date.now()-a} msec."


fix previews sometimes being rendered in wrong size [EDEGAL-24]

--- client/js/helpers/media_helper.coffee
+++ client/js/helpers/media_helper.coffee
@@ -5,8 +5,7 @@
 WRAP_VERTICAL_UNUSABLE_PX = 50 # account for footer (45px) and add a small epsilon for borders etc.

 exports.getPictureAreaDimensions = ->
-  wrap = document.getElementById 'wrap'
-  [wrap.offsetWidth, wrap.offsetHeight - WRAP_VERTICAL_UNUSABLE_PX]
+  [window.innerWidth, window.innerHeight - WRAP_VERTICAL_UNUSABLE_PX]

 exports.selectMedia = (picture) ->
   [maxWidth, maxHeight] = exports.getPictureAreaDimensions()


forced desktop page

forced desktop page

--- client/views/facebook/facebook.coffee
+++ client/views/facebook/facebook.coffee
@@ -42,7 +42,7 @@
             base = "https://www.facebook.com/sharer/sharer.php"
             title = encodeURIComponent title
             summary = encodeURIComponent description
-            href = base + "?s=100&p[url]=" + url + "&p[title]=" + title + "&p[summary]=" + summary
+            href = base + "?m2w&s=100&p[url]=" + url + "&p[title]=" + title + "&p[summary]=" + summary
             if img
                 href += "&p[images][0]=" + encodeURIComponent img


Add support for multi-key/value context node lookups

Empty lookups return false, only if each key/value pair match does it
return true.

--- coffee/cilantro/models/context.coffee
+++ coffee/cilantro/models/context.coffee
@@ -5,14 +5,19 @@
     class ContextNodeError extends Error

-    queryAttrs = (attrs, query, options) ->
+    queryAttrs = (attrs={}, query={}, options) ->
         if attrs instanceof ContextNodeModel
             attrs = attrs.attributes
-        if query.concept? and attrs.concept is query.concept
-            return true
-        if query.field? and attrs.field is query.field
-            return true
-        return false
+
+        # No empty queries
+        if c._.isEmpty(query) then return false
+
+        # Check against each key in the query for a match on attrs
+        for key, value of query
+            if attrs[key] isnt value
+                return false
+
+        return true

     # Represents a single node within a ContextModel tree. Non-branch nodes


Specify test/support directory globally in tests

--- test/gyp.coffee
+++ test/gyp.coffee
@@ -2,8 +2,10 @@
 fs = require 'fs'
 exec = require('child_process').exec

+supportDir = __dirname + '/support'
+
 execOptions =
-	cwd: __dirname + '/support'
+	cwd: supportDir

 execGruntTask = (task, callback) ->
 	exec "grunt gyp:#{task}", execOptions, (error) ->
@@ -18,18 +20,18 @@
 		fs.symlinkSync srcpath, dstpath

 linkBindingGyp = ->
-	if !fs.existsSync __dirname + '/support/binding.gyp'
-		createLink __dirname + '/support/binding.gyp.original', __dirname + '/support/binding.gyp'
+	if !fs.existsSync supportDir + '/binding.gyp'
+		createLink supportDir + '/binding.gyp.original', supportDir + '/binding.gyp'

 unlinkBindingGyp = ->
-	if fs.existsSync __dirname + '/support/binding.gyp'
-		fs.unlinkSync __dirname + '/support/binding.gyp'
+	if fs.existsSync supportDir + '/binding.gyp'
+		fs.unlinkSync supportDir + '/binding.gyp'

 rmBuildFiles = ->
 	# Even though there are more build files than config.gypi we just delete
 	# config.gypi as that is sufficient for testing purposes
-	if fs.existsSync __dirname + '/support/build/config.gypi'
-		fs.unlinkSync __dirname + '/support/build/config.gypi'
+	if fs.existsSync supportDir + '/build/config.gypi'
+		fs.unlinkSync supportDir + '/build/config.gypi'

 describe 'grunt-node-gyp', ->
 	# Set timeout to 120 seconds as compiling may take a long time.
@@ -42,7 +44,7 @@
 			execGruntTask 'configure', (err) ->
 				return done(err) if err

-				configGypi = fs.readFileSync __dirname + '/support/build/config.gypi', 'utf8'
+				configGypi = fs.readFileSync supportDir + '/build/config.gypi', 'utf8'
 				if configGypi.indexOf('"default_configuration": "Release"') < 0
 					return done(new Error 'expected config.gypi to be configured for release build')

@@ -54,7 +56,7 @@
 			execGruntTask 'configureDebug', (err) ->
 				return done(err) if err

-				configGypi = fs.readFileSync __dirname + '/support/build/config.gypi', 'utf8'
+				configGypi = fs.readFileSync supportDir + '/build/config.gypi', 'utf8'
 				if configGypi.indexOf('"default_configuration": "Debug"') < 0
 					return done(new Error 'expected config.gypi to be configured for debug build')

@@ -76,7 +78,7 @@
 				execGruntTask 'build', (err) ->
 					return done(err) if err

-					if !fs.existsSync __dirname + '/support/build/Release/hello_world.node'
+					if !fs.existsSync supportDir + '/build/Release/hello_world.node'
 						return done(new Error 'expected Release/hello_world.node to exist')

 					done()
@@ -90,7 +92,7 @@
 				execGruntTask 'buildDebug', (err) ->
 					return done(err) if err

-					if !fs.existsSync __dirname + '/support/build/Debug/hello_world.node'
+					if !fs.existsSync supportDir + '/build/Debug/hello_world.node'
 						return done(new Error 'expected Debug/hello_world.node to exist')

 					done()
@@ -106,7 +108,7 @@
 			execGruntTask 'clean', (err) ->
 				return done(err) if err

-				if fs.existsSync __dirname + '/support/build/'
+				if fs.existsSync supportDir + '/build/'
 					return done(new Error 'expected build directory to be removed')

 				done()
@@ -118,7 +120,7 @@
 			execGruntTask 'rebuild', (err) ->
 				return done(err) if err

-				if !fs.existsSync __dirname + '/support/build/Release/hello_world.node'
+				if !fs.existsSync supportDir + '/build/Release/hello_world.node'
 					return done(new Error 'expected Release/hello_world.node to exist')

 				done()
@@ -129,7 +131,7 @@
 			execGruntTask 'rebuildDebug', (err) ->
 				return done(err) if err

-				if !fs.existsSync __dirname + '/support/build/Debug/hello_world.node'
+				if !fs.existsSync supportDir + '/build/Debug/hello_world.node'
 					return done(new Error 'expected Debug/hello_world.node to exist')

 				done()
@@ -147,7 +149,7 @@
 			execGruntTask 'archIa32', (err) ->
 				return done(err) if err

-				configGypi = fs.readFileSync __dirname + '/support/build/config.gypi', 'utf8'
+				configGypi = fs.readFileSync supportDir + '/build/config.gypi', 'utf8'
 				if configGypi.indexOf('"target_arch": "ia32"') < 0
 					return done(new Error 'expected config.gypi to be configured for 32-bit build')

@@ -159,7 +161,7 @@
 			execGruntTask 'archX64', (err) ->
 				return done(err) if err

-				configGypi = fs.readFileSync __dirname + '/support/build/config.gypi', 'utf8'
+				configGypi = fs.readFileSync supportDir + '/build/config.gypi', 'utf8'
 				if configGypi.indexOf('"target_arch": "x64"') < 0
 					return done(new Error 'expected config.gypi to be configured for 64-bit build')

@@ -171,7 +173,7 @@
 			execGruntTask 'archArm', (err) ->
 				return done(err) if err

-				configGypi = fs.readFileSync __dirname + '/support/build/config.gypi', 'utf8'
+				configGypi = fs.readFileSync supportDir + '/build/config.gypi', 'utf8'
 				if configGypi.indexOf('"target_arch": "arm"') < 0
 					return done(new Error 'expected config.gypi to be configured for ARM build')

@@ -184,7 +186,7 @@
 			execGruntTask 'default', (err) ->
 				return done(err) if err

-				if !fs.existsSync __dirname + '/support/build/Release/hello_world.node'
+				if !fs.existsSync supportDir + '/build/Release/hello_world.node'
 					return done(new Error 'expected Release/hello_world.node to exist')

 				done()
@@ -195,7 +197,7 @@
 			execGruntTask 'defaultDebug', (err) ->
 				return done(err) if err

-				if !fs.existsSync __dirname + '/support/build/Debug/hello_world.node'
+				if !fs.existsSync supportDir + '/build/Debug/hello_world.node'
 					return done(new Error 'expected Debug/hello_world.node to exist')

 				done()