Removed FactlinkConfig

Removed FactlinkConfig

--- app/js/publisher_poc/nu.nl.coffee
+++ app/js/publisher_poc/nu.nl.coffee
@@ -6,23 +6,9 @@
   */
 (function(e,t,n){typeof module!="undefined"&&module.exports?module.exports=n():typeof define=="function"&&define.amd?define(n):t[e]=n()})("$script",this,function(){function v(e,t){for(var n=0,r=e.length;n<r;++n)if(!t(e[n]))return f;return 1}function m(e,t){v(e,function(e){return!t(e)})}function g(e,t,a){function d(e){return e.call?e():r[e]}function b(){if(!--p){r[h]=1,c&&c();for(var e in s)v(e.split("|"),d)&&!m(s[e],d)&&(s[e]=[])}}e=e[l]?e:[e];var f=t&&t.call,c=f?t:a,h=f?e.join(""):t,p=e.length;return setTimeout(function(){m(e,function(e){if(e===null)return b();if(u[e])return h&&(i[h]=1),u[e]==2&&b();u[e]=1,h&&(i[h]=1),y(!n.test(e)&&o?o+e+".js":e,b)})},0),g}function y(n,r){var i=e.createElement("script"),s=f;i.onload=i.onerror=i[d]=function(){if(i[h]&&!/^c|loade/.test(i[h])||s)return;i.onload=i[d]=null,s=1,u[n]=2,r()},i.async=1,i.src=n,t.insertBefore(i,t.firstChild)}var e=document,t=e.getElementsByTagName("head")[0],n=/^https?:\/\//,r={},i={},s={},o,u={},a="string",f=!1,l="push",c="DOMContentLoaded",h="readyState",p="addEventListener",d="onreadystatechange";return!e[h]&&e[p]&&(e[p](c,function b(){e.removeEventListener(c,b,f),e[h]="complete"},f),e[h]="loading"),g.get=y,g.order=function(e,t,n){(function r(i){i=e.shift(),e.length?g(i,r):g(i,t,n)})()},g.path=function(e){o=e},g.ready=function(e,t,n){e=e[l]?e:[e];var i=[];return!m(e,function(e){r[e]||i[l](e)})&&v(e,function(e){return r[e]})?t():!function(e){s[e]=s[e]||[],s[e][l](t),n&&n(i)}(e.join("|")),g},g.done=function(e){g([null],e)},g})
 `
-localConfig =
-  api: "http://localhost:3000"
-  lib: "http://localhost:8000/lib/dist"
-  srcPath: "/factlink.core.js"
-  url: window.location.href
-
-remoteConfig =
-  api: "https://factlink-testserver.inverselink.com:443"
-  lib: "https://factlink-static-testserver.inverselink.com:443/lib/dist"
-  srcPath: "/factlink.core.min.js"
-  url: window.location.href
-
-window.FactlinkConfig = localConfig
-
 $script(['//code.jquery.com/jquery-1.10.2.min.js',
          '//cdnjs.cloudflare.com/ajax/libs/underscore.js/1.5.2/underscore-min.js',
-         "#{window.FactlinkConfig.lib}/factlink_loader_basic.js"
+         "#{window.FactlinkConfig.lib}/factlink_loader_basic.#{window.FactlinkConfig.minified}js"
          ], 'myBundle')

 $script.ready 'myBundle', ->


collision with regular bounds

collision with regular bounds

--- src/collision.coffee
+++ src/collision.coffee
@@ -221,7 +221,7 @@
           yw = target.height/2
           return Collision.rayRectangle source, direction,
             x: target.x + xw
-            y
+            y: target.y +
             xw: xw
             yw: yw
         else


#0 #done 添加统计汇总

#0 #done 添加统计汇总

--- biz/BaseEntity.coffee
+++ biz/BaseEntity.coffee
@@ -4,7 +4,12 @@
 class BaseEntity
   constructor: (@member)->
     throw new Error('必需提供member参数') if not @member
-    #
+
+  #统计汇总
+  count: (condition, cb)->
+    entity = @entity()
+    query = entity.where(condition).select(entity.knex.raw('count(*)'))
+    @scalar query.toString(), cb

   #获取第一行第一列的数据
   scalar: (sql, cb)->


:art: rename and cleanup for being descriptive for stay feature

--- lib/operator.coffee
+++ lib/operator.coffee
@@ -78,28 +78,31 @@
       point = markers[i].getStartBufferPosition()
       selection.cursor.setBufferPosition(point)

-  getRestoreCursorType: ->
-    switch
-      when not @restoreCursor?
-        null
-      when settings.get(@restoreCursor.config)
-          , (@target.isLinewise?() and @stayOnLinewise)
-        @restoreCursor.stay
-      else
-        @restoreCursor.default
-
+  getCursorPositionInstruction: ->
+    return null unless @cursorPositionOnOperate?
+    {config, stayOnLinewise, stay, default: _default} = @cursorPositionOnOperate
+    if settings.get(config) or (@target.isLinewise?() and stayOnLinewise)
+      stay
+    else
+      _default
+
+  # restoreCursor would be set to one of following value,
+  #  point: original cursor position
+  #  marker: mark original cursor position
+  #  start: start point of selection after @target.select()
+  #  firstChar: first char of start line of selection after @target.select()
   eachSelection: (fn) ->
     select = =>
       @target.select() unless @haveSomeSelection()
-    restoreCursor = @getRestoreCursorType()
-    switch restoreCursor
-      when 'point'
+
+    switch instruction = @getCursorPositionInstruction()
+      when 'pointBeforeSelect'
         restore = @preservePoints()
         select()
-      when 'marker'
+      when 'pointBeforeSelectAsMarker'
         restore = @preservePointsAsMarker()
         select()
-      when 'start', 'firstChar'
+      when 'startPointOfSelection', 'firstCharacterOfStartLineOfSelection'
         select()
         restore = @preservePoints()
       else
@@ -110,7 +113,7 @@
       for selection, i in @editor.getSelections()
         @flash selection.getBufferRange(), -> fn(selection)
         restore?(selection, i)
-        if restoreCursor is 'firstChar'
+        if instruction is 'firstCharacterOfStartLineOfSelection'
           selection.cursor.moveToFirstCharacterOfLine()

 class Select extends Operator
@@ -143,8 +146,11 @@

 class TransformString extends Operator
   @extend(false)
-  restoreCursor: default: 'start', stay: 'point', config: 'stayOnTransformString'
-  stayOnLinewise: true
+  cursorPositionOnOperate:
+    default: 'startPointOfSelection'
+    stay: 'pointBeforeSelect'
+    config: 'stayOnTransformString'
+    stayOnLinewise: true

   execute: ->
     @eachSelection (s) =>
@@ -165,7 +171,7 @@

 class ToggleCaseAndMoveRight extends ToggleCase
   @extend()
-  restoreCursor: null
+  cursorPositionOnOperate: null
   hover: null
   preCompose: 'MoveRight'

@@ -289,8 +295,11 @@
 class Yank extends Operator
   @extend()
   hover: icon: ':yank:', emoji: ':clipboard:'
-  restoreCursor: default: 'start', stay: 'point', config: 'stayOnYank'
-  stayOnLinewise: true
+  cursorPositionOnOperate:
+    default: 'startPointOfSelection'
+    stay: 'pointBeforeSelect'
+    config: 'stayOnYank'
+    stayOnLinewise: true

   execute: ->
     @eachSelection (s) =>
@@ -417,7 +426,10 @@
 class Indent extends Operator
   @extend()
   hover: icon: ':indent:', emoji: ':point_right:'
-  restoreCursor: default: 'firstChar', stay: 'point', config: 'stayOnIndent'
+  cursorPositionOnOperate:
+    default: 'firstCharacterOfStartLineOfSelection'
+    stay: 'pointBeforeSelect'
+    config: 'stayOnIndent'

   execute: ->
     @eachSelection (s) =>
@@ -496,7 +508,10 @@
 class ReplaceWithRegister extends Operator
   @extend()
   hover: icon: ':replace-with-register:', emoji: ':pencil:'
-  restoreCursor: default: 'start', stay: 'point', config: 'stayOnReplaceWithRegister'
+  cursorPositionOnOperate:
+    default: 'startPointOfSelection'
+    stay: 'pointBeforeSelect'
+    config: 'stayOnReplaceWithRegister'
   execute: ->
     @eachSelection (s) =>
       newText = @vimState.register.getText() ? s.getText()
@@ -506,7 +521,8 @@
 class ToggleLineComments extends Operator
   @extend()
   hover: icon: ':toggle-line-comment:', emoji: ':mute:'
-  restoreCursor: default: 'marker'
+  cursorPositionOnOperate:
+    default: 'pointBeforeSelectAsMarker'
   execute: ->
     @eachSelection (s) ->
       s.toggleLineComments()


Modifying js to match server

Modifying js to match server

--- maproulette/static/js/maproulette.coffee
+++ maproulette/static/js/maproulette.coffee
@@ -169,6 +169,15 @@
   button.content = label
   return button

+makeChallengeSelectionDlg = (challenges) ->
+  ###
+  # Creates a dialog box for challenge selection
+  ###
+  dlg = $('

').addClass("dlg")
+  for c in challenges
+
+
+
 makeDlg = (dlgData) ->
   ###
   # Takes dialog box data and returns a dialog box for nextUp actions
@@ -318,7 +327,7 @@
   ###
   # Displays a task to the display and waits for the user prompt
   ###
-  drawFeatures(task.features)
+  drawFeatures(task.manifest)
   revGeocode()
   setDelay 3, msgClose()
   msgTaskText()


prevent timed auto-removal of re-added keys

--- src/keyring.coffee
+++ src/keyring.coffee
@@ -34,6 +34,7 @@
     @guestKeys = {}
     for r in @registry
       @guestKeys[r] = @storage.get("guest[#{r}]")
+    @guestKeyTimeouts = {}

   commFromSeed: (seed) ->
     @commKey = Nacl.fromSeed Nacl.encode_utf8 seed
@@ -92,10 +93,12 @@
     return null unless strGuestTag and strPubKey
     strPubKey = strPubKey.trimLines()
     @guestKeys[strGuestTag] = strPubKey
-    Utils.delay Config.RELAY_SESSION_TIMEOUT, =>
+    if @guestKeyTimeouts[strGuestTag]
+      clearTimeout @guestKeyTimeouts[strGuestTag]
+    @guestKeyTimeouts[strGuestTag] = Utils.delay Config.RELAY_SESSION_TIMEOUT, =>
       delete @guestKeys[strGuestTag]
-      @emit 'tempGuestTimeout',
-        strGuestTag: strGuestTag
+      delete @guestKeyTimeouts[strGuestTag]
+      @emit 'tmpguesttimeout', strGuestTag

   removeGuest: (strGuestTag) ->
     return null unless strGuestTag and @guestKeys[strGuestTag]


Revert "use a single pdfjs worker, rotated to avoid leaks"

This reverts commit f270ef54911acd800d2ffd577080b50ea2fe4aff.

Use a new worker on each reload to free up memory.

--- services/web/public/coffee/ide/pdfng/directives/pdfRenderer.coffee
+++ services/web/public/coffee/ide/pdfng/directives/pdfRenderer.coffee
@@ -5,22 +5,6 @@
 	# App = angular.module 'PDFRenderer', ['pdfAnnotations', 'pdfTextLayer']

 	App.factory 'PDFRenderer', ['$q', '$timeout', 'pdfAnnotations', 'pdfTextLayer', 'pdfSpinner', ($q, $timeout, pdfAnnotations, pdfTextLayer, pdfSpinner) ->
-
-		# Have a single worker used by all rendering, to avoid reloading
-		RenderThread = { worker: null, count: 0}
-
-		getRenderThread = () ->
-			if RenderThread.count > 16 # recycle the worker periodically to avoid leaks
-				RenderThread.readyToDestroy = true
-				RenderThread = { worker: null, count: 0 }
-			RenderThread.worker ||= new PDFJS.PDFWorker('pdfjsworker')
-			RenderThread.count++
-			return RenderThread
-
-		resetWorker = (thread) ->
-			thread.worker.destroy() if thread.readyToDestroy
-
-		# The PDF page renderer

 		class PDFRenderer
 			JOB_QUEUE_INTERVAL: 25
@@ -43,8 +27,7 @@
 				# PDFJS.disableStream
 				# PDFJS.disableRange
 				@scale = @options.scale || 1
-				@thread = getRenderThread()
-				@pdfjs = PDFJS.getDocument {url: @url, rangeChunkSize: 2*65536, worker: @thread.worker}
+				@pdfjs = PDFJS.getDocument {url: @url, rangeChunkSize: 2*65536}
 				@pdfjs.onProgress = @options.progressCallback
 				@document = $q.when(@pdfjs)
 				@navigateFn = @options.navigateFn
@@ -353,9 +336,8 @@
 			destroy: () ->
 				@shuttingDown = true
 				@resetState()
-				@pdfjs.then (document) =>
+				@pdfjs.then (document) ->
 					document.cleanup()
 					document.destroy()
-					resetWorker(@thread)

 		]


Updated Coffee Script to include previous button feature.

--- bootstrap-tour.coffee
+++ bootstrap-tour.coffee
@@ -7,9 +7,9 @@
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
 # You may obtain a copy of the License at
-#
+#
 #     http://www.apache.org/licenses/LICENSE-2.0
-#
+#
 # Unless required by applicable law or agreed to in writing, software
 # distributed under the License is distributed on an "AS IS" BASIS,
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -34,6 +34,11 @@
       $(document).on "click", ".popover .next", (e) =>
         e.preventDefault()
         @next()
+
+      # Go to previous step after click on element with class .prev
+      $(document).on "click", ".popover .prev", (e) =>
+        e.preventDefault()
+        @prev()

       # End tour after click on element with class .end
       $(document).on "click", ".popover .end", (e) =>
@@ -61,6 +66,7 @@
         title: "",
         content: "",
         next: i + 1,
+        prev:i - 1,
         end: i == @_steps.length - 1,
         animation: true
       }, @_steps[i])
@@ -74,6 +80,11 @@
     next: ->
       @hideStep(@_current)
       @showNextStep()
+
+    # Hide current step and show prev step
+    prev: ->
+      @hideStep(@_current)
+      @showPrevStep()

     # End tour
     end: ->
@@ -153,13 +164,26 @@
       step = @getStep(@_current)
       @showStep(step.next)

+    # Show next step
+    showPrevStep: ->
+      step = @getStep(@_current)
+      @showStep(step.prev)
+
+    # Show prev step
+    showPrevStep: ->
+      step = @getStep(@_current)
+      @showStep(step.prev)
+
     # Show step popover
     _showPopover: (step, i) ->
       content = "#{step.content}

"
-      if step.end
-        content += "End"
+      if step.prev < 0
+        content += "Next » End Tour"
+      else if step.end
+        content += "« PrevEnd Tour"
       else
-        content += "Next »
+        content += "« Prev
+        Next »
           End tour
"

       $(step.element).popover({


Removed debug statements

Removed debug statements

--- src/goo/loaders/DynamicLoader.coffee
+++ src/goo/loaders/DynamicLoader.coffee
@@ -270,10 +270,8 @@
 						@_configs[ref] = data
 				else
 					if @_isBinaryRef(ref)
-						console.log "#{ref} is binary"
 						mode = Ajax.ARRAY_BUFFER
 					else
-						console.log "#{ref} is ascii"
 						mode = null
 					@_configs[ref] = @_ajax.load(url, mode)
 					.then (data)=>


Send components-changed only when there are actual changes, fixes #83

--- src/library.coffee
+++ src/library.coffee
@@ -123,20 +123,29 @@

   _updateComponents: (components) ->
     names = Object.keys components
+    updated = false
     for name, comp of components
       if not comp
         # removed
         @components[name] = null
+        updated = true
         continue
       existing = @getComponent name
       unless existing
         # added
         @components[name] = comp
+        updated = true
         continue
       # update
       for k, v of comp
+        if existing[k]
+          # Check if value has changed
+          continue if JSON.stringify(existing[k]) is JSON.stringify(v)
         existing[k] = v
-
+        updated = true
+
+    # Skip sending components-changed if nothing changed
+    return unless updated
     @emit 'components-changed', names, @components

   load: (callback) ->


Minor enhancement

Minor enhancement

--- client.coffee
+++ client.coffee
@@ -116,10 +116,10 @@

     try
       _URL = window.URL || window.webkitURL || window.mozURL || window.msURL || window.oURL || false
-      if window?.Worker and window?.Blob and _URL
+      if window.Worker and window.Blob and _URL
         @_supportWebWorker = true
         @_webWorkerUrl     = _URL.createObjectURL(new Blob(['!function(a){"use strict";a.onmessage=function(b){var c=b.data.f.slice(b.data.cs*(b.data.cc-1),b.data.cs*b.data.cc);if(b.data.ib===!0)postMessage({bin:c,chunkId:b.data.cc});else{var d;a.FileReader?(d=new FileReader,d.onloadend=function(a){postMessage({bin:(d.result||a.srcElement||a.target).split(",")[1],chunkId:b.data.cc,s:b.data.s})},d.onerror=function(a){throw(a.target||a.srcElement).error},d.readAsDataURL(c)):a.FileReaderSync?(d=new FileReaderSync,postMessage({bin:d.readAsDataURL(c).split(",")[1],chunkId:b.data.cc})):postMessage({bin:null,chunkId:b.data.cc,error:"File API is not supported in WebWorker!"})}}}(this);'], {type: 'application/javascript'}))
-      else if window?.Worker
+      else if window.Worker
         @_supportWebWorker = true
         @_webWorkerUrl     = Meteor.absoluteUrl 'packages/ostrio_files/worker.min.js'
       else


Minor '=>' use

Minor '=>' use

--- client.coffee
+++ client.coffee
@@ -488,7 +488,7 @@
 ###

 DHT::_resolveContacts = (contacts, done) ->
-  tasks = contacts.map((contact) ->
+  tasks = contacts.map((contact) =>
     (cb) ->
       addrData = addrToIPPort(contact.addr)
       if isIP(addrData[0])


Progress bar refinements

Progress bar refinements

+ Possible fix for double capture notifications.

--- client.coffee
+++ client.coffee
@@ -102,95 +102,98 @@
 				backgroundColor: Db.shared.peek('colors', teamId, 'hex')

 addProgressBar = ->
-	log 'Render progress bar outer'
-	Db.shared.observeEach 'game', 'beacons', (beacon) !->
-		log 'Render progress bar 1 beacon'
-		Obs.onClean ->
-			log 'Cleaning progress bar...'
-		if beacon.get('inRange', Plugin.userId())?
-			dbPercentage = beacon.get("percentage")
-			nextPercentage = -1
-			nextColor = ""
-			owner = beacon.get('owner')
-			nextOwner = beacon.get('nextOwner')
-			actionStarted = beacon.get("actionStarted")
-			log 'Action started ', new Date()/1000-actionStarted, ' seconds ago'
-			action = beacon.get('action')
-			barText = ""
-			if action == "capture"
-				nextPercentage=100
-				dbPercentage += (new Date() /1000 -actionStarted)/30 * 100
-				#dbPercentage -= 9.8
-				log "actionStarted = " + actionStarted
-				if dbPercentage > 100
-					dbPercentage = 100
-				if dbPercentage < 0
-					dbPercentage = 0
-				nextColor = Db.shared.peek('colors', nextOwner, 'hex')
-				barText = "Capturing..."
-			else if action == "neutralize"
-				nextPercentage=0
-				dbPercentage -= (new Date() /1000 -actionStarted)/30 * 100
-				if dbPercentage < 0
-					dbPercentage = 0
-				if dbPercentage > 100
-					dbPercentage = 100
-				nextColor = Db.shared.peek('colors', owner, 'hex')
-				barText = "Neutralizing..."
-			else
-				nextPercentage = dbPercentage
-				if beacon.get("owner")==-1
+	Obs.observe ->
+		log 'Render progress bar outer'
+		Db.shared.iterate 'game', 'beacons', (beacon) !->
+			action = beacon.get('action') # Subscribe to changes in action, only thing that matters
+			log 'action=', action
+			if beacon.peek('inRange', Plugin.userId())?
+				log 'Rendering progress bar'
+				Obs.onClean ->
+					log 'Cleaned progress bar...'
+				dbPercentage = beacon.peek("percentage")
+				nextPercentage = -1
+				nextColor = ""
+				owner = beacon.peek('owner')
+				nextOwner = beacon.peek('nextOwner')
+				actionStarted = beacon.peek("actionStarted")
+				log 'Action started ', new Date()/1000-actionStarted, ' seconds ago'
+				barText = ""
+				if action == "capture"
+					nextPercentage=100
+					dbPercentage += (new Date() /1000 -actionStarted)/30 * 100
+					#dbPercentage -= 9.8
+					log "actionStarted = " + actionStarted
+					if dbPercentage > 100
+						dbPercentage = 100
+					if dbPercentage < 0
+						dbPercentage = 0
 					nextColor = Db.shared.peek('colors', nextOwner, 'hex')
+					barText = "Capturing..."
+				else if action == "neutralize"
+					nextPercentage=0
+					dbPercentage -= (new Date() /1000 -actionStarted)/30 * 100
+					if dbPercentage < 0
+						dbPercentage = 0
+					if dbPercentage > 100
+						dbPercentage = 100
+					nextColor = Db.shared.peek('colors', owner, 'hex')
+					barText = "Neutralizing..."
 				else
-					nextColor = Db.shared.peek('colors', owner, 'hex')
-				barText = "Competing with others..."
-			if dbPercentage == 100 and owner == nextOwner
-				barText = "Captured"
-			time = 0
-
-			if nextPercentage != dbPercentage
-				time = Math.abs(dbPercentage-nextPercentage) * 300
-			log "nextPercentage = ", nextPercentage, ", dbPercentage = ", dbPercentage, ", time = ", time, ", action = ", action
-			Dom.div !->
-				Dom.style
-					height: "25px"
-					width: "100%"
-					zIndex: "5"
-					backgroundColor: "rgba(243,243,243,0.3)"
-					border: 0
-					boxShadow: "0 3px 10px 0 rgba(0, 0, 0, 1)"
-					marginBottom: '-25px'
-					overflowX: "hidden"
+					nextPercentage = dbPercentage
+					if owner == -1
+						nextColor = Db.shared.peek('colors', nextOwner, 'hex')
+					else
+						nextColor = Db.shared.peek('colors', owner, 'hex')
+					barText = "Competing with others..."
+				if dbPercentage == 100 and owner == nextOwner
+					barText = "Captured"
+				time = 0
+
+				if nextPercentage != dbPercentage
+					time = Math.abs(dbPercentage-nextPercentage) * 300
+				log "nextPercentage = ", nextPercentage, ", dbPercentage = ", dbPercentage, ", time = ", time, ", action = ", action
 				Dom.div !->
 					Dom.style
 						height: "25px"
-						#background: '-moz-linear-gradient(top,  rgba(0,0,0,0) 0%, rgba(0,0,0,0.3) 100%)'
-						#background: '-webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(0,0,0,0)), color-stop(100%,rgba(0,0,0,0.3)))'
-						#background: '-webkit-linear-gradient(top,  rgba(0,0,0,0) 0%,rgba(0,0,0,0.3) 100%)'
-						#background: '-o-linear-gradient(top,  rgba(0,0,0,0) 0%,rgba(0,0,0,0.3) 100%)'
-						#background: '-ms-linear-gradient(top,  rgba(0,0,0,0) 0%,rgba(0,0,0,0.3) 100%)'
-						background: 'linear-gradient(to bottom,  rgba(0,0,0,0) 0%,rgba(0,0,0,0.3) 100%)'
-						filter: "progid:DXImageTransform.Microsoft.gradient( startColorstr='#00000000', endColorstr='#4d000000',GradientType=0 )"
-						backgroundColor: nextColor
-						zIndex: "10"
-						_borderRadius: '2px'
-						padding: '0 13px 0 2px'
-						marginLeft: '-2px'
-					Dom._get().style.width = dbPercentage + "%"
-					log "dbPercentage after balancing = ", dbPercentage
-					Dom._get().style.transition = "width " + time + "ms linear"
-					window.progressElement = Dom._get()
-					timer = () -> window.progressElement.style.width = nextPercentage + "%"
-					window.setTimeout(timer, 100)
-				Dom.div !->
-					Dom.text barText
-					Dom.style
-						width: '100%'
-						color: 'white'
-						marginTop: '-22px'
-						textAlign: 'center'
-						fontSize: '15px'
-						_textShadow: '0 0 5px #000000, 0 0 5px #000000' # Double for extra visibility
+						width: "100%"
+						zIndex: "5"
+						backgroundColor: "rgba(243,243,243,0.3)"
+						border: 0
+						boxShadow: "0 3px 10px 0 rgba(0, 0, 0, 1)"
+						marginBottom: '-25px'
+						#overflowX: 'hidden'
+					Dom.div !->
+						Dom.style
+							height: "25px"
+							#background: '-moz-linear-gradient(top,  rgba(0,0,0,0) 0%, rgba(0,0,0,0.3) 100%)'
+							#background: '-webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(0,0,0,0)), color-stop(100%,rgba(0,0,0,0.3)))'
+							#background: '-webkit-linear-gradient(top,  rgba(0,0,0,0) 0%,rgba(0,0,0,0.3) 100%)'
+							#background: '-o-linear-gradient(top,  rgba(0,0,0,0) 0%,rgba(0,0,0,0.3) 100%)'
+							#background: '-ms-linear-gradient(top,  rgba(0,0,0,0) 0%,rgba(0,0,0,0.3) 100%)'
+							background: 'linear-gradient(to bottom,  rgba(0,0,0,0) 0%,rgba(0,0,0,0.3) 100%)'
+							filter: "progid:DXImageTransform.Microsoft.gradient( startColorstr='#00000000', endColorstr='#4d000000',GradientType=0 )"
+							backgroundColor: nextColor
+							zIndex: "10"
+							_borderRadius: '12.5px'
+							padding: '0 13px 0 13px'
+							marginLeft: '-13px'
+						Dom._get().style.width = dbPercentage + "%"
+						log "dbPercentage after balancing = ", dbPercentage
+						Dom._get().style.transition = "width " + time + "ms linear"
+						window.progressElement = Dom._get()
+						timer = () -> window.progressElement.style.width = nextPercentage + "%"
+						window.setTimeout(timer, 100)
+					Dom.div !->
+						Dom.text barText
+						Dom.style
+							width: '100%'
+							color: 'white'
+							marginTop: '-22px'
+							textAlign: 'center'
+							fontSize: '15px'
+							_textShadow: '0 0 5px #000000, 0 0 5px #000000' # Double for extra visibility
+

 # Home page with map
 mainContent = ->
@@ -960,7 +963,7 @@
 					Obs.observe ->
 						if Db.shared.peek('gameState') is 1 # Only when the game is running, do something
 							log 'Checking beacon takeover'
-							Db.shared.observeEach 'game', 'beacons', (beacon) !->
+							Db.shared.iterate 'game', 'beacons', (beacon) !->
 								beaconCoord = L.latLng(beacon.peek('location', 'lat'), beacon.peek('location', 'lng'))
 								distance = latLngObj.distanceTo(beaconCoord)
 								#log 'distance=', distance, 'beacon=', beacon
@@ -971,9 +974,9 @@
 										log 'UserID', Plugin.userId()
 										log 'UserLoc', latLngObj
 									if inRange
-										log 'Taking over beacon: userId=', Plugin.userId(), ', location=', latLngObj
+										log 'Trying beacon takeover: userId=', Plugin.userId(), ', location=', latLngObj
 									else
-										log 'Stopping takeover of beacon: userId=', Plugin.userId(), ', location=', latLngObj
+										log 'Trying stop of beacon takeover: userId=', Plugin.userId(), ', location=', latLngObj
 			else
 				log 'Location could not be found'
 			Obs.onClean ->


some cleanup on client

some cleanup on client

--- client.coffee
+++ client.coffee
@@ -73,44 +73,55 @@
     @schema_manager = new SchemaManager(options.schemas...)
     @directory = options.directory
     @authorizer = options.authorizer
-    @resources = {}

     @resource_definitions = options.resources

-    @representation_ids = {}
-    @resource_constructors = {}
-
-    for id, schema of @schema_manager.ids
+    @representations = @create_representation_constructors(@schema_manager.ids)
+    @resource_constructors = @create_resource_constructors(options.resources, @representations)
+
+    @resources = @create_resources(@directory, @resource_constructors)
+
+  create_representation_constructors: (schemas) ->
+    constructors = {}
+    for id, schema of schemas
       if schema.type == "array"
         constructor = @array_wrapper(schema)
-        @representation_ids[schema.id] = constructor
+        constructors[schema.id] = constructor
       else if schema.type == "object"
         constructor = @object_wrapper(schema)
-        @representation_ids[schema.id] = constructor
+        constructors[schema.id] = constructor
       else if !SchemaManager.is_primitive(schema.type)
         constructor = @representation_constructor(schema)
-        @representation_ids[schema.id] = constructor
-
-    for resource_type, definition of @resource_definitions
-      for id, constructor of @representation_ids
+        constructors[schema.id] = constructor
+      else
+        console.warn "Received unexpected schema type:", schema.type
+    return constructors
+
+
+  create_resource_constructors: (definitions, representations) ->
+    constructors = {}
+    for resource_type, definition of definitions
+      for id, constructor of representations
         # The assumption here is that the frag-ident part of a schema id
         # corresponds to the resource type.
         [base, name] = id.split("#")
         if name == resource_type
-          @resourcify(constructor, resource_type)
-          @resource_constructors[resource_type] = constructor
+          @resourcify(constructor, resource_type, definition)
+          constructors[resource_type] = constructor

       # create a resource constructor for types with no directly associated schemas.
-      @resource_constructors[resource_type] ||= @resourcify(null, resource_type)
-
-    @create_resources(@directory)
+      constructors[resource_type] ||= @resourcify(null, resource_type, definition)
+    return constructors
+

   # Create resource instances using the URLs supplied in the service
   # description's directory.
-  create_resources: (directory) ->
+  create_resources: (directory, constructors) ->
+    resources = {}
     for key, value of directory
-      if @resource_constructors[key]
-        @resources[key] = new @resource_constructors[key](url: value)
+      if constructors[key]
+        resources[key] = new constructors[key](url: value)
+    return resources

   array_wrapper: (schema) ->
     client = @
@@ -160,7 +171,7 @@
       schema.type

   wrap: (type, data) ->
-    if wrapper = @representation_ids[type]
+    if wrapper = @representations[type]
       new wrapper(data)
     else
       data
@@ -203,8 +214,7 @@

-
-  resourcify: (constructor, resource_type) ->
+  resourcify: (constructor, resource_type, definition) ->
     client = @
     constructor ||= (@properties) ->
       @url = @properties.url
@@ -218,9 +228,7 @@
       value: client
       enumerable: false

-    if definition = @resource_definitions[resource_type]
-      @define_actions(constructor, definition.actions)
-
+    @define_actions(constructor, definition.actions)
     constructor

   define_actions: (constructor, actions) ->
@@ -285,9 +293,12 @@
         url: resource.url
         method: method
         headers: {}
-        content: options.content
         query: options.query
         on: {}
+      if options.body
+        request.body = options.body
+      else if options.content
+        request.content = options.content

       # verify presence of the required query params from the schema
       for key, value of required_params


Fix some typos

Fix some typos

--- app/scripts/views/ItemView.coffee
+++ app/scripts/views/ItemView.coffee
@@ -7,7 +7,7 @@
     if ok then parentView else undefined

-# test if the given object is an Ember object or has a get method and use it to reutrn a proeprty
+# test if the given object is an Ember object or has a get method and use it to return a property
 getProperty = (obj, prop, noGetReturns) ->
     return noGetReturns unless Ember.typeOf(obj) is 'instance' or Ember.canInvoke(obj, 'get')
     obj.get prop
@@ -46,4 +46,4 @@
         itemsView.set('selected', null) if disabled and @get 'isActive'
         disabled
     ).property('content', 'content.disabled').cacheable()
-)+)


Working Traits

Working Traits

--- src/client/traits/index.coffee
+++ src/client/traits/index.coffee
@@ -1,17 +1,43 @@
+_        = require '../../utils/lodash'
+assign   = require '../../polyfills/assign'
+
 class Trait
   constructor: (api, name, value)->
     @_api = api
     @name = name
-    @set(value) if value?
+    if value?
+      # If value is an object, then it has to be compatible
+      if _.isObject value
+        # new Trait('a_number', {value: 20, operation: 'dec'});
+        @raw assign({},value,{name:@name})
+      else
+        # new Trait('a_number', 20);
+        @set value

   inc: (step = 1)->
-    @_api('me/traits', 'put', { name: @name, operation: 'inc', value: step })
+    @raw({ name: @name, operation: 'inc', value: value })

   dec: (step = 1)->
-    @_api('me/traits', 'put', { name: @name, operation: 'dec', value: step })
+    @raw({ name: @name, operation: 'dec', value: value })

   set: (value)->
-    @_api('me/traits', 'put', { name: @name, operation: 'set', value: value })
+    @raw({ name: @name, operation: 'set', value: value })
+
+  raw: (payload)->
+    @_api.message('me/traits', 'put', payload)
+

 module.exports = (api)->
-  (name, value)-> new Trait(api, name, value)
+  (name, value)->
+    # We've been passed a Hash, iterate on it to create a trait for each.
+    if _.isObject(name)
+        # Hull.traits({
+        #   'a_number'  : {value: 20, operation: 'dec'}
+        #   'a_number_2': {value: 20, operation: 'dec'}
+        #   'a_number_3', 20
+        # });
+      _.map name, (value, key)-> new Trait(api, key, value)
+    else
+      # Hull.traits('a_number', {value: 20, operation: 'dec'});
+      # Hull.traits('a_number' 20);
+      new Trait(api, name, value)


Client: Auto-focus on refresh button for new client modal.

--- client/app/js/concerns/stale_assets.coffee
+++ client/app/js/concerns/stale_assets.coffee
@@ -1,5 +1,6 @@
 PokeBattle.events.on 'version', (version) ->
   if version != PokeBattle.CLIENT_VERSION
-    PokeBattle.modal 'modals/new_client', ($modal) ->
+    $modal = PokeBattle.modal 'modals/new_client', ($modal) ->
       $modal.on 'click', '.button_refresh', ->
         window.location.reload(true)
+    $modal.find('button').first().focus()


Refactored sendComment method and Tests methods

--- client/test/lib/teams/teamschannels.coffee
+++ client/test/lib/teams/teamschannels.coffee
@@ -15,6 +15,7 @@

   sendComment: (browser) ->
+    message = helpers.getFakeText()

     message = helpers.getFakeText()

@@ -82,6 +83,8 @@

   postLongMessage: (browser) ->
+    message  = ''
+    message += helpers.getFakeText() for [1..6]

     message  = ''
     message += helpers.getFakeText() for [1..6]


better request termination for attachment upload

--- server/controllers/attachments.coffee
+++ server/controllers/attachments.coffee
@@ -61,6 +61,7 @@
                     # file.
                     log.info "Attachment #{name} saved to Couch."
                     res.send 201, success: true
+                    next()

             part.pipe stream

@@ -72,8 +73,10 @@
         next err

     form.on 'close', ->
-        res.send 400, error: 'No file sent' if nofile
-        next()
+        if nofile
+            err = new Error 'no file sent'
+            err.status = 400
+            next err

 # GET /data/:id/attachments/:name
@@ -105,6 +108,7 @@
             stream.pipe res

+
 # DELETE /data/:id/attachments/:name
 # Remove given attachment (represented by name) from given document
 # (represented by id).


Remove the 'invalid argument' warning

Remove the 'invalid argument' warning

It might be an arg for electron/node/chrome...
-> #922

--- lib/cl.coffee
+++ lib/cl.coffee
@@ -58,7 +58,7 @@
     when checkShowVersion(arg) then return
     when parseDebugOptions(arg) then return
     # when parseWhateverOtherOptions(arg) then return
-    else warn "Invalid argument (ignored): #{arg}"
+    # else - unrecognized argument, just ignore.

 # Finish initialization of debug environment
 Debug.init()


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/metered.coffee
+++ src/metered.coffee
@@ -27,7 +27,8 @@
 # => Sets up a health meter that will be drawn during the enemy overlay event.
 # Since maxHealth wasn't provided, it is set to the value of I.health (500)

-someOtherObject.meter 'turbo'
+someOtherObject.meter
+  name: 'turbo'
 # => Sets up a turbo meter that will be drawn during the someOtherObject overlay event.
 # Since neither turbo maxTurbo were provided, they are both set to 100.


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/metered.coffee
+++ src/metered.coffee
@@ -21,7 +21,7 @@
       if not I[name]
         I[name] = 100

-      if not I["max#{}"]
+      if not I["max#{name.ca}"]

       ratio = I[name] / I["max#{name.capitalize()}"]


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/metered.coffee
+++ src/metered.coffee
@@ -1,7 +1,7 @@
 ###*
 The Metered module provides a simple drop-in
 meter ui to track arbitrary numeric attributes.
-
+If

 player = Core


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/metered.coffee
+++ src/metered.coffee
@@ -127,7 +127,7 @@
   @methodOf Metered#
   @param {Object} options The meter configuration options
     borderColor - The color of the meter's border
-    borderWidth - The width of the meter's
+    borderWidth - The width of the meter's border
   ###
   meter: (options={}) ->
     Object.reverseMerge options,


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/metered.coffee
+++ src/metered.coffee
@@ -150,7 +150,7 @@
         I[I.name] = 100

       if not I["max#{I.name.capitalize()}"]
-        if I[name]
+        if I[I.name]
           I["max#{I.name.capitalize()}"] = I[name]
         else
           I["max#{I.name.capitalize()}"] = 100


removing bogus comparison

removing bogus comparison

--- src/natives.coffee
+++ src/natives.coffee
@@ -178,8 +178,7 @@
             _this.$type instanceof types.ArrayType
         o 'getSuperclass()L!/!/!;', (rs, _this) ->
             type = _this.$type
-            if (type instanceof types.PrimitiveType) or type == 'Ljava/lang/Object;'
-              return null
+            return null if type instanceof types.PrimitiveType
             cls = rs.class_lookup type
             if cls.access_flags.interface or not cls.super_class?
               return null


getBundle is no longer used

getBundle is no longer used

--- src/natives.coffee
+++ src/natives.coffee
@@ -11,13 +11,6 @@

 # things assigned to root will be available outside this module
 root = exports ? this.natives = {}
-
-getBundle = (rs, base_name) ->
-  # load in the default ResourceBundle (ignores locale)
-  classname = util.int_classname rs.jvm2js_str(base_name)
-  rs.push (b_ref = rs.init_object classname)
-  rs.method_lookup({class: classname, sig: '<init>()V'}).run(rs)
-  b_ref

 # convenience function. idea taken from coffeescript's grammar
 o = (fn_name, fn) -> fn_name: fn_name, fn: fn


Return when grammars are already loaded

Return when grammars are already loaded

--- src/package.coffee
+++ src/package.coffee
@@ -179,6 +179,8 @@
       fs.listSync(stylesheetDirPath, ['css', 'less'])

   loadGrammarsSync: ->
+    return if @grammarsLoaded
+
     @grammars = []
     grammarsDirPath = path.join(@path, 'grammars')
     grammarPaths = fs.listSync(grammarsDirPath, ['json', 'cson'])


Cache paths are relative to resource path

--- src/package.coffee
+++ src/package.coffee
@@ -195,13 +195,13 @@

   loadKeymaps: ->
     if @bundledPackage and packagesCache[@name]?
-      @keymaps = (["#{@path}#{path.sep}#{keymapPath}", keymapObject] for keymapPath, keymapObject of packagesCache[@name].keymaps)
+      @keymaps = (["#{atom.packages.resourcePath}#{path.sep}#{keymapPath}", keymapObject] for keymapPath, keymapObject of packagesCache[@name].keymaps)
     else
       @keymaps = @getKeymapPaths().map (keymapPath) -> [keymapPath, CSON.readFileSync(keymapPath)]

   loadMenus: ->
     if @bundledPackage and packagesCache[@name]?
-      @menus = (["#{@path}#{path.sep}#{menuPath}", menuObject] for menuPath, menuObject of packagesCache[@name].menus)
+      @menus = (["#{atom.packages.resourcePath}#{path.sep}#{menuPath}", menuObject] for menuPath, menuObject of packagesCache[@name].menus)
     else
       @menus = @getMenuPaths().map (menuPath) -> [menuPath, CSON.readFileSync(menuPath)]


Add explicit return after while loop

Add explicit return after while loop

--- src/package.coffee
+++ src/package.coffee
@@ -449,6 +449,7 @@
                 @activateNow()
                 break
               currentTarget = currentTarget.parentElement
+            return
     return

   getActivationCommands: ->


Added check if element exists

Added check if element exists

--- clickingmonkey.coffee
+++ clickingmonkey.coffee
@@ -6,9 +6,9 @@
   delay = (ms)->
     new Promise (resolve, reject)-> setTimeout resolve, ms
   doClick = (selector) ->
-    (document.querySelector selector).click()
+    (document.querySelector selector)?.click()
   doType = (selector, text) ->
-    (document.querySelector selector).value = text
+    (document.querySelector selector)?.value = text

   @clickElem = (selector, ms=1000) =>
     promise = promise.then ->


Fix affix resizing in all cases (fix #484)

--- client/message.coffee
+++ client/message.coffee
@@ -215,18 +215,18 @@
   @autorun ->
     setTitle titleOrUntitled Template.currentData()

-$(window).resize ->
+$(window).resize affixResize = _.debounce ->
+  $('.affix').height $(window).height()
   $('.affix-top').height $(window).height() - $('#top').outerHeight true
+, 100
 Template.message.onRendered ->
   $('body').scrollspy
     target: 'nav.contents'
   $('nav.contents').affix
     offset: top: $('#top').outerHeight true
-  $('.affix-top').height $(window).height() - $('#top').outerHeight true
-  $('nav.contents').on 'affixed.bs.affix', ->
-    $('.affix').height $(window).height()
-  $('nav.contents').on 'affixed-top.bs.affix', ->
-    $('.affix-top').height $(window).height() - $('#top').outerHeight true
+  affixResize()
+  $('nav.contents').on 'affixed.bs.affix', affixResize
+  $('nav.contents').on 'affixed-top.bs.affix', affixResize
   tooltipInit()

   ## Give focus to first Title input, if there is one.


Bumped version of dashboard

Bumped version of dashboard

--- client/version.coffee
+++ client/version.coffee
@@ -1 +1 @@
-@version = '2.15.2'
+@version = '2.15.3'


go green

go green

--- test/color2.coffee
+++ test/color2.coffee
@@ -1,155 +1,161 @@
+( ->
+
+equalEnough = (actual, expected, tolerance=3, message) ->
+  message ||= "" + expected + " within " + tolerance + " of " + actual
+  ok(expected + tolerance >= actual && expected - tolerance <= actual, message)
+
 module "Color2"

 test "r, g, b default to 0, a defaults to 1", ->
   color = Color2()

-  equal color.r, 0
-  equal color.g, 0
-  equal color.b, 0
-  equal color.a, 1
+  equalEnough color.r, 0
+  equalEnough color.g, 0
+  equalEnough color.b, 0
+  equalEnough color.a, 1

 test "accepts a single array of length 3", ->
   color = Color2([255, 1, 2])

-  equal color.r, 255
-  equal color.g, 1
-  equal color.b, 2
+  equalEnough color.r, 255
+  equalEnough color.g, 1
+  equalEnough color.b, 2

 test "accepts a single array of length 4", ->
   color = Color2([255, 4, 3, 0.8])

-  equal color.r, 255
-  equal color.g, 4
-  equal color.b, 3
-  equal color.a, 0.8
+  equalEnough color.r, 255
+  equalEnough color.g, 4
+  equalEnough color.b, 3
+  equalEnough color.a, 0.8

 test "accepts an array of length 3 as the first argument and an alpha value as the second", ->
   color = Color2([100, 5, 3], 0.4)

-  equal color.r, 100
-  equal color.g, 5
-  equal color.b, 3
-  equal color.a, 0.4
+  equalEnough color.r, 100
+  equalEnough color.g, 5
+  equalEnough color.b, 3
+  equalEnough color.a, 0.4

 test "accepts an array of length 4 as the first argument and an alpha value. The alpha value passed as the second argument overrides the alpha in the array", ->
   color = Color2([4, 20, 90, 0.1], 0.6)

-  equal color.r, 4
-  equal color.g, 20
-  equal color.b, 90
-  equal color.a, 0.6
+  equalEnough color.r, 4
+  equalEnough color.g, 20
+  equalEnough color.b, 90
+  equalEnough color.a, 0.6

 test "accepts an rgb string as the first argument and an alpha value as the second", ->
   color = Color2('rgb(255, 40, 30)', 0.4)

-  equal color.r, 255
-  equal color.g, 40
-  equal color.b, 30
-  equal color.a, 0.4
+  equalEnough color.r, 255
+  equalEnough color.g, 40
+  equalEnough color.b, 30
+  equalEnough color.a, 0.4

 test "accepts an rgba string as the first argument and an alpha value as the second. The alpha value takes precedence over the alpha from the rgba string", ->
   color = Color2('rgba(30, 20, 15, 0.8)', 0.35)

-  equal color.r, 30
-  equal color.g, 20
-  equal color.b, 15
-  equal color.a, 0.35
+  equalEnough color.r, 30
+  equalEnough color.g, 20
+  equalEnough color.b, 15
+  equalEnough color.a, 0.35

 test "accepts an hsl string as the first argument and an alpha value as the second", ->
   color = Color2('hsl(63, 0.4, 0.3)', 0.21)

-  equal color.r, 104
-  equal color.g, 107
-  equal color.b, 46
-  equal color.a, 0.21
+  equalEnough color.r, 104
+  equalEnough color.g, 107
+  equalEnough color.b, 46
+  equalEnough color.a, 0.21

 test "accepts an hsla string as the first argument and an alpha value as the second. The alpha value takes precedence over the hsla string alpha value", ->
   color = Color2('hsla(45, 0.3, 0.5, 0.1)', 0.47)

-  equal color.r, 166
-  equal color.g, 147
-  equal color.b, 89
-  equal color.a, 0.47
+  equalEnough color.r, 166
+  equalEnough color.g, 147
+  equalEnough color.b, 89
+  equalEnough color.a, 0.47

 test "accepts hex value of length 3 as first argument and alpha value as second argument", ->
   color = Color2('#ff0', 0.5)

-  equal color.r, 255
-  equal color.g, 255
-  equal color.b, 0
-  equal color.a, 0.5
+  equalEnough color.r, 255
+  equalEnough color.g, 255
+  equalEnough color.b, 0
+  equalEnough color.a, 0.5

 test "accepts hex value of length 4 as first argument and alpha value as second argument. The alpha value takes precedence over the alpha from the hex string.", ->
   color = Color2('#ef3c', 0.3)

-  equal color.r, 238
-  equal color.g, 255
-  equal color.b, 51
-  equal color.a, 0.3
+  equalEnough color.r, 238
+  equalEnough color.g, 255
+  equalEnough color.b, 51
+  equalEnough color.a, 0.3

 test "accepts hex value of length 6 as first argument and alpha value as second argument", ->
   color = Color2('#de34ab', 0.27)

-  equal color.r, 222
-  equal color.g, 52
-  equal color.b, 171
-  equal color.a, 0.27
+  equalEnough color.r, 222
+  equalEnough color.g, 52
+  equalEnough color.b, 171
+  equalEnough color.a, 0.27

 test "accepts hex value of length 8 as first argument and alpha value as second argument. Alpha value takes precedence over hex alpha.", ->
   color = Color2('#ab45cd', 0.59)

-  equal color.r, 171
-  equal color.g, 69
-  equal color.b, 205
-  equal color.a, 0.59
+  equalEnough color.r, 171
+  equalEnough color.g, 69
+  equalEnough color.b, 205
+  equalEnough color.a, 0.59

 test "accepts hex string of length 3", ->
   color = Color2('#df2')

-  equal color.r, 221
-  equal color.g, 255
-  equal color.b, 34
-  equal color.a, 1.0
+  equalEnough color.r, 221
+  equalEnough color.g, 255
+  equalEnough color.b, 34
+  equalEnough color.a, 1.0

 test "accepts hex string of length 4", ->
   color = Color2('#c987')

-  equal color.r, 204
-  equal color.g, 153
-  equal color.b, 136
-  equal color.a.toFixed(2), 0.47
+  equalEnough color.r, 204
+  equalEnough color.g, 153
+  equalEnough color.b, 136
+  equalEnough color.a.toFixed(2), 0.47

 test "accepts hex string of length 6", ->
   color = Color2('#cdef12')

-  equal color.r, 205
-  equal color.g, 239
-  equal color.b, 18
-  equal color.a, 1.0
+  equalEnough color.r, 205
+  equalEnough color.g, 239
+  equalEnough color.b, 18
+  equalEnough color.a, 1.0

 test "accepts hex string of length 8", ->
   color = Color2('#ef1234af')

-  equal color.r, 239
-  equal color.g, 18
-  equal color.b, 52
-  equal color.a.toFixed(2), 0.69
+  equalEnough color.r, 239
+  equalEnough color.g, 18
+  equalEnough color.b, 52
+  equalEnough color.a.toFixed(2), 0.69

 test "accepts 3 numeric values", ->
   color = Color2(34, 54, 39)

-  equal color.r, 34
-  equal color.g, 54
-  equal color.b, 39
-  equal color.a, 1.0
+  equalEnough color.r, 34
+  equalEnough color.g, 54
+  equalEnough color.b, 39
+  equalEnough color.a, 1.0

 test "accepts 4 numeric values", ->
   color = Color2(23, 43, 100, 0.32)

-  equal color.r, 23
-  equal color.g, 43
-  equal color.b, 100
-  equal color.a, 0.32
+  equalEnough color.r, 23
+  equalEnough color.g, 43
+  equalEnough color.b, 100
+  equalEnough color.a, 0.32

 test "invalid color throws error", ->
   raises ->
@@ -187,150 +193,150 @@

   hsl = color.toHsl()

-  equal hsl[0].round(), 22
-  equal hsl[1].toFixed(2), 0.38
-  equal hsl[2].toFixed(2), 0.13
-  equal hsl[3], 1.0
+  equalEnough hsl[0].round(), 22
+  equalEnough hsl[1].toFixed(2), 0.38
+  equalEnough hsl[2].toFixed(2), 0.13
+  equalEnough hsl[3], 1.0

 test "#hue", ->
   color = Color2(34, 54, 239)

-  color2 = color.hue(60)
-
-  equal color2.r, 218
-  equal color2.g, 37
-  equal color2.b, 239
-  equal color2.a, 1.0
+  color2 = color.hue(20)
+
+  equalEnough color2.r, 84
+  equalEnough color2.g, 37
+  equalEnough color2.b, 239
+  equalEnough color2.a, 1.0

 test "#hue$", ->
   color = Color2(34, 54, 239)

   color.hue$(60)

-  equal color.r, 218
-  equal color.g, 37
-  equal color.b, 239
-  equal color.a, 1.0
+  equalEnough color.r, 218
+  equalEnough color.g, 37
+  equalEnough color.b, 239
+  equalEnough color.a, 1.0

 test "#complement", ->
   color = Color2(10, 30, 50)

   color2 = color.complement()

-  equal color2.r, 51
-  equal color2.g, 31
-  equal color2.b, 10
-  equal color2.a, 1.0
+  equalEnough color2.r, 51
+  equalEnough color2.g, 31
+  equalEnough color2.b, 10
+  equalEnough color2.a, 1.0

 test "#complement$", ->
   color = Color2(10, 30, 50)

   color.complement$()

-  equal color.r, 51
-  equal color.g, 31
-  equal color.b, 10
-  equal color.a, 1.0
+  equalEnough color.r, 51
+  equalEnough color.g, 31
+  equalEnough color.b, 10
+  equalEnough color.a, 1.0

 test "#grayscale", ->
   color = Color2(30, 40, 29)

   color2 = color.grayscale()

-  equal color2.r, 35
-  equal color2.g, 35
-  equal color2.b, 35
-  equal color2.a, 1.0
+  equalEnough color2.r, 35
+  equalEnough color2.g, 35
+  equalEnough color2.b, 35
+  equalEnough color2.a, 1.0

 test "#grayscale$", ->
   color = Color2(30, 40, 29)

   color.grayscale$()

-  equal color.r, 35
-  equal color.g, 35
-  equal color.b, 35
-  equal color.a, 1.0
+  equalEnough color.r, 35
+  equalEnough color.g, 35
+  equalEnough color.b, 35
+  equalEnough color.a, 1.0

 test "#saturate", ->
   color = Color2(100, 200, 150)

   color2 = color.saturate(0.3)

-  equal color2.r, 69
-  equal color2.g, 232
-  equal color2.b, 150
-  equal color2.a, 1.0
+  equalEnough color2.r, 69
+  equalEnough color2.g, 232
+  equalEnough color2.b, 150
+  equalEnough color2.a, 1.0

 test "#saturate$", ->
   color = Color2(100, 200, 150)

   color.saturate$(0.3)

-  equal color.r, 69
-  equal color.g, 232
-  equal color.b, 150
-  equal color.a, 1.0
+  equalEnough color.r, 69
+  equalEnough color.g, 232
+  equalEnough color.b, 150
+  equalEnough color.a, 1.0

 test "#desaturate", ->
   color = Color2(69, 232, 150)

   color2 = color.desaturate(0.3)

-  equal color2.r, 100
-  equal color2.g, 200
-  equal color2.b, 150
-  equal color2.a, 1.0
+  equalEnough color2.r, 100
+  equalEnough color2.g, 200
+  equalEnough color2.b, 150
+  equalEnough color2.a, 1.0

 test "#desaturate$", ->
   color = Color2(69, 232, 150)

   color.desaturate$(0.3)

-  equal color.r, 100
-  equal color.g, 200
-  equal color.b, 150
-  equal color.a, 1.0
+  equalEnough color.r, 100
+  equalEnough color.g, 200
+  equalEnough color.b, 150
+  equalEnough color.a, 1.0

 test "#darken", ->
   color = Color2(45, 64, 39)

   color2 = color.darken(0.1)

-  equal color2.r, 22
-  equal color2.g, 32
-  equal color2.b, 19
-  equal color2.a, 1.0
+  equalEnough color2.r, 22
+  equalEnough color2.g, 32
+  equalEnough color2.b, 19
+  equalEnough color2.a, 1.0

 test "#darken$", ->
   color = Color2(45, 64, 39)

   color.darken$(0.1)

-  equal color.r, 22
-  equal color.g, 32
-  equal color.b, 19
-  equal color.a, 1.0
+  equalEnough color.r, 22
+  equalEnough color.g, 32
+  equalEnough color.b, 19
+  equalEnough color.a, 1.0

 test "#lighten", ->
   color = Color2(22, 32, 19)

   color2 = color.lighten(0.1)

-  equal color2.r, 45
-  equal color2.g, 64
-  equal color2.b, 39
-  equal color2.a, 1.0
+  equalEnough color2.r, 45
+  equalEnough color2.g, 64
+  equalEnough color2.b, 39
+  equalEnough color2.a, 1.0

 test "#lighten$", ->
   color = Color2(22, 32, 19)

   color.lighten$(0.1)

-  equal color.r, 45
-  equal color.g, 64
-  equal color.b, 39
-  equal color.a, 1.0
+  equalEnough color.r, 45
+  equalEnough color.g, 64
+  equalEnough color.b, 39
+  equalEnough color.a, 1.0

 test "#mixWith", ->
   color1 = Color2(50, 40, 60, 0.3)
@@ -338,17 +344,17 @@

   mixedColor = color1.mixWith(color2)

-  equals mixedColor.r, 30
-  equals mixedColor.g, (45 / 2).round()
-  equals mixedColor.b, (76 / 2).round()
-  equals mixedColor.a, 0.5 / 2
+  equalEnough mixedColor.r, 30
+  equalEnough mixedColor.g, (45 / 2).round()
+  equalEnough mixedColor.b, (76 / 2).round()
+  equalEnough mixedColor.a, 0.5 / 2

   weightedMixedColor = color1.mixWith(color2, 0.1)

-  equals weightedMixedColor.r, ((50 * 0.1) + (10 * 0.9)).round()
-  equals weightedMixedColor.g, ((40 * 0.1) + (5 * 0.9)).round()
-  equals weightedMixedColor.b, ((60 * 0.1) + (16 * 0.9)).round()
-  equals weightedMixedColor.a, (0.3 * 0.1) + (0.2 * 0.9)
+  equalEnough weightedMixedColor.r, ((50 * 0.1) + (10 * 0.9)).round()
+  equalEnough weightedMixedColor.g, ((40 * 0.1) + (5 * 0.9)).round()
+  equalEnough weightedMixedColor.b, ((60 * 0.1) + (16 * 0.9)).round()
+  equalEnough weightedMixedColor.a, (0.3 * 0.1) + (0.2 * 0.9)

 test "#mixWith$", ->
   color1 = Color2(50, 40, 60, 0.3)
@@ -358,17 +364,17 @@

   color1.mixWith$(color2)

-  equals color1.r, 30
-  equals color1.g, (45 / 2).round()
-  equals color1.b, (76 / 2).round()
-  equals color1.a, 0.5 / 2
+  equalEnough color1.r, 30
+  equalEnough color1.g, (45 / 2).round()
+  equalEnough color1.b, (76 / 2).round()
+  equalEnough color1.a, 0.5 / 2

   color3.mixWith$(color2, 0.1)

-  equals color3.r, ((50 * 0.1) + (10 * 0.9)).round()
-  equals color3.g, ((40 * 0.1) + (5 * 0.9)).round()
-  equals color3.b, ((60 * 0.1) + (16 * 0.9)).round()
-  equals color3.a, (0.3 * 0.1) + (0.2 * 0.9)
+  equalEnough color3.r, ((50 * 0.1) + (10 * 0.9)).round()
+  equalEnough color3.g, ((40 * 0.1) + (5 * 0.9)).round()
+  equalEnough color3.b, ((60 * 0.1) + (16 * 0.9)).round()
+  equalEnough color3.a, (0.3 * 0.1) + (0.2 * 0.9)

 test "Color.random", ->
   color = Color2.random()
@@ -376,7 +382,7 @@
   ok 0 <= color.r <= 255
   ok 0 <= color.g <= 255
   ok 0 <= color.b <= 255
-  equal color.a, 1.0
+  equalEnough color.a, 1.0

 test "Color.mix", ->
   color1 = Color2(50, 40, 60, 0.3)
@@ -384,32 +390,33 @@

   mixedColor = Color2.mix(color1, color2)

-  equals mixedColor.r, 30
-  equals mixedColor.g, (45 / 2).round()
-  equals mixedColor.b, (76 / 2).round()
-  equals mixedColor.a, 0.5 / 2
+  equalEnough mixedColor.r, 30
+  equalEnough mixedColor.g, (45 / 2).round()
+  equalEnough mixedColor.b, (76 / 2).round()
+  equalEnough mixedColor.a, 0.5 / 2

   weightedMixedColor = Color2.mix(color1, color2, 0.1)

-  equals weightedMixedColor.r, ((50 * 0.1) + (10 * 0.9)).round()
-  equals weightedMixedColor.g, ((40 * 0.1) + (5 * 0.9)).round()
-  equals weightedMixedColor.b, ((60 * 0.1) + (16 * 0.9)).round()
-  equals weightedMixedColor.a, (0.3 * 0.1) + (0.2 * 0.9)
+  equalEnough weightedMixedColor.r, ((50 * 0.1) + (10 * 0.9)).round()
+  equalEnough weightedMixedColor.g, ((40 * 0.1) + (5 * 0.9)).round()
+  equalEnough weightedMixedColor.b, ((60 * 0.1) + (16 * 0.9)).round()
+  equalEnough weightedMixedColor.a, (0.3 * 0.1) + (0.2 * 0.9)

 test "accepts named colors", ->
   white = Color2("white")

-  equals white.r, 255
-  equals white.g, 255
-  equals white.b, 255
-  equals white.a, 1.0
+  equalEnough white.r, 255
+  equalEnough white.g, 255
+  equalEnough white.b, 255
+  equalEnough white.a, 1.0

   piPink = Color2("Paul Irish Pink")

-  equals piPink.r, 255
-  equals piPink.g, 94
-  equals piPink.b, 153
-  equals piPink.a, 1.0
+  equalEnough piPink.r, 255
+  equalEnough piPink.g, 94
+  equalEnough piPink.b, 153
+  equalEnough piPink.a, 1.0

 module()

+)()


improved dateObservable tests

improved dateObservable tests

--- test/config.coffee
+++ test/config.coffee
@@ -56,163 +56,271 @@

   describe 'dateObservable', ->
     describe '#fit()', ->
-      describe 'minDate', ->
+      describe 'minDate = 2015-05-14, period = month', ->
         describe 'inclusive mode', ->
-          it 'should work', () ->
-            c = new Config({
-              period: 'month'
-              minDate: [moment.utc('2015-05-14'), 'inclusive']
-              maxDate: [moment.utc('2016-01-01'), 'inclusive']
-            })
+          c = new Config({
+            period: 'month'
+            minDate: [moment.utc('2015-05-14'), 'inclusive']
+            maxDate: [moment.utc('2016-01-01'), 'inclusive']
+          })
+
+          it 'converts 2015-04-15 into 2015-05-14', () ->
             assert.equal(c.startDate.fit('2015-04-15').format(fmt), '2015-05-14')
+
+          it 'converts 2015-05-05 into 2015-05-14', () ->
             assert.equal(c.startDate.fit('2015-05-05').format(fmt), '2015-05-14')
+
+          it 'converts 2015-05-14 into 2015-05-14', () ->
             assert.equal(c.startDate.fit('2015-05-14').format(fmt), '2015-05-14')
+
+          it 'converts 2015-05-25 into 2015-05-25', () ->
             assert.equal(c.startDate.fit('2015-05-25').format(fmt), '2015-05-25')
+
+          it 'converts 2015-06-15 into 2015-06-15', () ->
             assert.equal(c.startDate.fit('2015-06-15').format(fmt), '2015-06-15')

         describe 'exclusive mode', ->
-          it 'should work', () ->
-            c = new Config({
-              period: 'month'
-              minDate: [moment.utc('2015-05-14'), 'exclusive']
-              maxDate: [moment.utc('2016-01-01'), 'inclusive']
-            })
+          c = new Config({
+            period: 'month'
+            minDate: [moment.utc('2015-05-14'), 'exclusive']
+            maxDate: [moment.utc('2016-01-01'), 'inclusive']
+          })
+
+          it 'converts 2015-04-15 into 2015-06-01', () ->
             assert.equal(c.startDate.fit('2015-04-15').format(fmt), '2015-06-01')
+
+          it 'converts 2015-05-05 into 2015-06-01', () ->
             assert.equal(c.startDate.fit('2015-05-05').format(fmt), '2015-06-01')
+
+          it 'converts 2015-05-14 into 2015-06-01', () ->
             assert.equal(c.startDate.fit('2015-05-14').format(fmt), '2015-06-01')
+
+          it 'converts 2015-05-25 into 2015-06-01', () ->
             assert.equal(c.startDate.fit('2015-05-25').format(fmt), '2015-06-01')
+
+          it 'converts 2015-06-15 into 2015-06-15', () ->
             assert.equal(c.startDate.fit('2015-06-15').format(fmt), '2015-06-15')

         describe 'extended mode', ->
-          it 'should work', () ->
-            c = new Config({
-              period: 'month'
-              minDate: [moment.utc('2015-05-14'), 'extended']
-              maxDate: [moment.utc('2016-01-01'), 'inclusive']
-            })
+          c = new Config({
+            period: 'month'
+            minDate: [moment.utc('2015-05-14'), 'extended']
+            maxDate: [moment.utc('2016-01-01'), 'inclusive']
+          })
+
+          it 'converts 2015-04-15 into 2015-05-01', () ->
             assert.equal(c.startDate.fit('2015-04-15').format(fmt), '2015-05-01')
+
+          it 'converts 2015-05-05 into 2015-05-05', () ->
             assert.equal(c.startDate.fit('2015-05-05').format(fmt), '2015-05-05')
+
+          it 'converts 2015-05-14 into 2015-05-14', () ->
             assert.equal(c.startDate.fit('2015-05-14').format(fmt), '2015-05-14')
+
+          it 'converts 2015-05-25 into 2015-05-25', () ->
             assert.equal(c.startDate.fit('2015-05-25').format(fmt), '2015-05-25')
+
+          it 'converts 2015-06-15 into 2015-06-15', () ->
             assert.equal(c.startDate.fit('2015-06-15').format(fmt), '2015-06-15')

-      describe 'maxDate', ->
+      describe 'maxDate = 2015-05-14, period = month', ->
         describe 'inclusive mode', ->
-          it 'should work', () ->
-            c = new Config({
-              period: 'month'
-              minDate: [moment.utc('2014-01-01'), 'inclusive']
-              maxDate: [moment.utc('2015-05-14'), 'inclusive']
-            })
+          c = new Config({
+            period: 'month'
+            minDate: [moment.utc('2014-01-01'), 'inclusive']
+            maxDate: [moment.utc('2015-05-14'), 'inclusive']
+          })
+
+          it 'converts 2015-04-15 into 2015-04-15', () ->
             assert.equal(c.startDate.fit('2015-04-15').format(fmt), '2015-04-15')
+
+          it 'converts 2015-05-05 into 2015-05-05', () ->
             assert.equal(c.startDate.fit('2015-05-05').format(fmt), '2015-05-05')
+
+          it 'converts 2015-05-14 into 2015-05-14', () ->
             assert.equal(c.startDate.fit('2015-05-14').format(fmt), '2015-05-14')
+
+          it 'converts 2015-05-25 into 2015-05-14', () ->
             assert.equal(c.startDate.fit('2015-05-25').format(fmt), '2015-05-14')
+
+          it 'converts 2015-06-15 into 2015-05-14', () ->
             assert.equal(c.startDate.fit('2015-06-15').format(fmt), '2015-05-14')

         describe 'exclusive mode', ->
-          it 'should work', () ->
-            c = new Config({
-              period: 'month'
-              minDate: [moment.utc('2014-01-01'), 'inclusive']
-              maxDate: [moment.utc('2015-05-14'), 'exclusive']
-            })
+          c = new Config({
+            period: 'month'
+            minDate: [moment.utc('2014-01-01'), 'inclusive']
+            maxDate: [moment.utc('2015-05-14'), 'exclusive']
+          })
+
+          it 'converts 2015-04-15 into 2015-04-15', () ->
             assert.equal(c.startDate.fit('2015-04-15').format(fmt), '2015-04-15')
+
+          it 'converts 2015-05-05 into 2015-04-30', () ->
             assert.equal(c.startDate.fit('2015-05-05').format(fmt), '2015-04-30')
+
+          it 'converts 2015-05-14 into 2015-04-30', () ->
             assert.equal(c.startDate.fit('2015-05-14').format(fmt), '2015-04-30')
+
+          it 'converts 2015-05-25 into 2015-04-30', () ->
             assert.equal(c.startDate.fit('2015-05-25').format(fmt), '2015-04-30')
+
+          it 'converts 2015-06-15 into 2015-04-30', () ->
             assert.equal(c.startDate.fit('2015-06-15').format(fmt), '2015-04-30')

         describe 'extended mode', ->
-          it 'should work', () ->
-            c = new Config({
-              period: 'month'
-              minDate: [moment.utc('2014-01-01'), 'inclusive']
-              maxDate: [moment.utc('2015-05-14'), 'extended']
-            })
+          c = new Config({
+            period: 'month'
+            minDate: [moment.utc('2014-01-01'), 'inclusive']
+            maxDate: [moment.utc('2015-05-14'), 'extended']
+          })
+
+          it 'converts 2015-04-15 into 2015-04-15', () ->
             assert.equal(c.startDate.fit('2015-04-15').format(fmt), '2015-04-15')
+
+          it 'converts 2015-05-05 into 2015-05-05', () ->
             assert.equal(c.startDate.fit('2015-05-05').format(fmt), '2015-05-05')
+
+          it 'converts 2015-05-14 into 2015-05-14', () ->
             assert.equal(c.startDate.fit('2015-05-14').format(fmt), '2015-05-14')
+
+          it 'converts 2015-05-25 into 2015-05-25', () ->
             assert.equal(c.startDate.fit('2015-05-25').format(fmt), '2015-05-25')
+
+          it 'converts 2015-06-15 into 2015-05-31', () ->
             assert.equal(c.startDate.fit('2015-06-15').format(fmt), '2015-05-31')

     describe '#isWithinBoundaries()', ->
-      describe 'minDate', ->
+      describe 'minDate = 2015-05-14, period = month', ->
         describe 'inclusive mode', ->
-          it 'should work', () ->
-            c = new Config({
-              period: 'month'
-              minDate: [moment.utc('2015-05-14'), 'inclusive']
-              maxDate: [moment.utc('2016-01-01'), 'inclusive']
-            })
+          c = new Config({
+            period: 'month'
+            minDate: [moment.utc('2015-05-14'), 'inclusive']
+            maxDate: [moment.utc('2016-01-01'), 'inclusive']
+          })
+
+          it 'returns false for 2015-04-15', () ->
             assert.isFalse(c.startDate.isWithinBoundaries('2015-04-15'), '2015-04-15')
+
+          it 'returns true for 2015-05-05', () ->
             assert.isTrue(c.startDate.isWithinBoundaries('2015-05-05'), '2015-05-05') # tricky one
+
+          it 'returns true for 2015-05-14', () ->
             assert.isTrue(c.startDate.isWithinBoundaries('2015-05-14'), '2015-05-14')
+
+          it 'returns true for 2015-05-25', () ->
             assert.isTrue(c.startDate.isWithinBoundaries('2015-05-25'), '2015-05-25')
+
+          it 'returns true for 2015-06-15', () ->
             assert.isTrue(c.startDate.isWithinBoundaries('2015-06-15'), '2015-06-15')

         describe 'exclusive mode', ->
-          it 'should work', () ->
-            c = new Config({
-              period: 'month'
-              minDate: [moment.utc('2015-05-14'), 'exclusive']
-              maxDate: [moment.utc('2016-01-01'), 'inclusive']
-            })
+          c = new Config({
+            period: 'month'
+            minDate: [moment.utc('2015-05-14'), 'exclusive']
+            maxDate: [moment.utc('2016-01-01'), 'inclusive']
+          })
+
+          it 'returns false for 2015-04-15', () ->
             assert.isFalse(c.startDate.isWithinBoundaries('2015-04-15'), '2015-04-15')
+
+          it 'returns false for 2015-05-05', () ->
             assert.isFalse(c.startDate.isWithinBoundaries('2015-05-05'), '2015-05-05')
+
+          it 'returns false for 2015-05-14', () ->
             assert.isFalse(c.startDate.isWithinBoundaries('2015-05-14'), '2015-05-14')
+
+          it 'returns false for 2015-05-25', () ->
             assert.isFalse(c.startDate.isWithinBoundaries('2015-05-25'), '2015-05-25')
+
+          it 'returns true for 2015-06-15', () ->
             assert.isTrue(c.startDate.isWithinBoundaries('2015-06-15'), '2015-06-15')

         describe 'extended mode', ->
-          it 'should work', () ->
-            c = new Config({
-              period: 'month'
-              minDate: [moment.utc('2015-05-14'), 'extended']
-              maxDate: [moment.utc('2016-01-01'), 'inclusive']
-            })
+          c = new Config({
+            period: 'month'
+            minDate: [moment.utc('2015-05-14'), 'extended']
+            maxDate: [moment.utc('2016-01-01'), 'inclusive']
+          })
+
+          it 'returns false for 2015-04-15', () ->
             assert.isFalse(c.startDate.isWithinBoundaries('2015-04-15'), '2015-04-15')
+
+          it 'returns true for 2015-05-05', () ->
             assert.isTrue(c.startDate.isWithinBoundaries('2015-05-05'), '2015-05-05')
+
+          it 'returns true for 2015-05-14', () ->
             assert.isTrue(c.startDate.isWithinBoundaries('2015-05-14'), '2015-05-14')
+
+          it 'returns true for 2015-05-25', () ->
             assert.isTrue(c.startDate.isWithinBoundaries('2015-05-25'), '2015-05-25')
+
+          it 'returns true for 2015-06-15', () ->
             assert.isTrue(c.startDate.isWithinBoundaries('2015-06-15'), '2015-06-15')

-      describe 'maxDate', ->
+      describe 'maxDate = 2015-05-14, period = month', ->
         describe 'inclusive mode', ->
-          it 'should work', () ->
-            c = new Config({
-              period: 'month'
-              minDate: [moment.utc('2014-01-01'), 'inclusive']
-              maxDate: [moment.utc('2015-05-14'), 'inclusive']
-            })
+          c = new Config({
+            period: 'month'
+            minDate: [moment.utc('2014-01-01'), 'inclusive']
+            maxDate: [moment.utc('2015-05-14'), 'inclusive']
+          })
+
+          it 'returns true for 2015-04-15', () ->
             assert.isTrue(c.startDate.isWithinBoundaries('2015-04-15'), '2015-04-15')
+
+          it 'returns true for 2015-05-05', () ->
             assert.isTrue(c.startDate.isWithinBoundaries('2015-05-05'), '2015-05-05')
+
+          it 'returns true for 2015-05-14', () ->
             assert.isTrue(c.startDate.isWithinBoundaries('2015-05-14'), '2015-05-14')
+
+          it 'returns true for 2015-05-25', () ->
             assert.isTrue(c.startDate.isWithinBoundaries('2015-05-25'), '2015-05-25') # tricky one
+
+          it 'returns false for 2015-06-15', () ->
             assert.isFalse(c.startDate.isWithinBoundaries('2015-06-15'), '2015-06-15')

         describe 'exclusive mode', ->
-          it 'should work', () ->
-            c = new Config({
-              period: 'month'
-              minDate: [moment.utc('2014-01-01'), 'inclusive']
-              maxDate: [moment.utc('2015-05-14'), 'exclusive']
-            })
+          c = new Config({
+            period: 'month'
+            minDate: [moment.utc('2014-01-01'), 'inclusive']
+            maxDate: [moment.utc('2015-05-14'), 'exclusive']
+          })
+
+          it 'returns true for 2015-04-15', () ->
             assert.isTrue(c.startDate.isWithinBoundaries('2015-04-15'), '2015-04-15')
+
+          it 'returns false for 2015-05-05', () ->
             assert.isFalse(c.startDate.isWithinBoundaries('2015-05-05'), '2015-05-05')
+
+          it 'returns false for 2015-05-14', () ->
             assert.isFalse(c.startDate.isWithinBoundaries('2015-05-14'), '2015-05-14')
+
+          it 'returns false for 2015-05-25', () ->
             assert.isFalse(c.startDate.isWithinBoundaries('2015-05-25'), '2015-05-25')
+
+          it 'returns false for 2015-06-15', () ->
             assert.isFalse(c.startDate.isWithinBoundaries('2015-06-15'), '2015-06-15')

         describe 'extended mode', ->
-          it 'should work', () ->
-            c = new Config({
-              period: 'month'
-              minDate: [moment.utc('2014-01-01'), 'inclusive']
-              maxDate: [moment.utc('2015-05-14'), 'extended']
-            })
+          c = new Config({
+            period: 'month'
+            minDate: [moment.utc('2014-01-01'), 'inclusive']
+            maxDate: [moment.utc('2015-05-14'), 'extended']
+          })
+
+          it 'returns true for 2015-04-15', () ->
             assert.isTrue(c.startDate.isWithinBoundaries('2015-04-15'), '2015-04-15')
+
+          it 'returns true for 2015-05-05', () ->
             assert.isTrue(c.startDate.isWithinBoundaries('2015-05-05'), '2015-05-05')
+
+          it 'returns true for 2015-05-14', () ->
             assert.isTrue(c.startDate.isWithinBoundaries('2015-05-14'), '2015-05-14')
+
+          it 'returns true for 2015-05-25', () ->
             assert.isTrue(c.startDate.isWithinBoundaries('2015-05-25'), '2015-05-25')
+
+          it 'returns false for 2015-06-15', () ->
             assert.isFalse(c.startDate.isWithinBoundaries('2015-06-15'), '2015-06-15')


enabling compilation of non-.coffee-extension files passed directly to the coffee command (Issue #391)

--- command.coffee
+++ command.coffee
@@ -61,12 +61,12 @@
   compile_scripts()

 # Asynchronously read in each CoffeeScript in a list of source files and
-# compile them. If a directory is passed, recursively compile all source
-# files in it and all subdirectories.
+# compile them. If a directory is passed, recursively compile all
+# '.coffee' extension source files in it and all subdirectories.
 compile_scripts: ->
   for source in sources
     base: source
-    compile: (source) ->
+    compile: (source, top_level) ->
       path.exists source, (exists) ->
         throw new Error "File not found: $source" unless exists
         fs.stat source, (err, stats) ->
@@ -74,10 +74,10 @@
             fs.readdir source, (err, files) ->
               for file in files
                 compile path.join(source, file)
-          else if path.extname(source) is '.coffee'
+          else if top_level or path.extname(source) is '.coffee'
             fs.readFile source, (err, code) -> compile_script(source, code.toString(), base)
             watch source, base if options.watch
-    compile source
+    compile source, true

 # Compile a single source script, containing the given code, according to the
 # requested options. If evaluating the script directly sets `__filename`,


:memo: add publish -t example

:memo: add publish -t example

--- src/publish.coffee
+++ src/publish.coffee
@@ -23,6 +23,7 @@
     options.usage """

       Usage: apm publish [<newversion> | major | minor | patch | build]
+             apm publish -t <tagname>

       Publish a new version of the package in the current working directory.


improving messages and setting command to "build json"

--- scripts/example.coffee
+++ scripts/example.coffee
@@ -21,7 +21,7 @@
       res.reply 'The current HEAD of TM Design is: ' + body.json_Parse().object.sha

-   robot.respond 'json',(res) ->
+   robot.respond 'build json',(res) ->
      res.reply "About to trigger build of tm-4-0-json-data repository"
      options =
        url    : 'https://ci.appveyor.com/api/builds',
@@ -30,14 +30,9 @@
          'Content-Type': 'application/x-www-form-urlencoded'
        body   : 'accountName=DinisCruz&projectSlug=tm-4-0-json-data&branch=master'

-
-     res.reply options.json_Pretty()
-
-
      request.post options, (error, response, data)->
-       res.reply error
-       res.reply data.json_Parse().json_Pretty()
-
+       res.reply 'Request sent, take a look at https://ci.appveyor.com/project/DinisCruz/tm-4-0-json-data where a new build should have just started'
+       #res.reply data.json_Parse().json_Pretty()

    robot.hear /badger/i, (res) ->


Verify usernames when registering.

Verify usernames when registering.

--- server/accounts.coffee
+++ server/accounts.coffee
@@ -1,5 +1,14 @@
 ADMIN_USER_ID = 'NfEBPKH6GLYHuSJXJ'
 ADMIN_PERSON_ID = 'exYYMzAP6a2swNRCx'
+
+USERNAME_REGEX = /^[a-zA-Z0-9_-]+$/
+
+FORBIDDEN_USERNAMES = [
+  'webmster'
+  'root'
+  'peerlibrary'
+  'administrator'
+]

 Accounts.onCreateUser (options, user) ->
   try
@@ -8,6 +17,19 @@
       personId = ADMIN_PERSON_ID
     else
       personId = Random.id()
+
+    # We are verifying things here and not in a validateNewUser hook to prevent creation
+    # of a profile document and then failure later on when validating through validateNewUser
+
+    # TODO: Our error messages end with a dot, but client-side (Meteor's) do not
+
+    throw new Meteor.Error 403, "Username must be at least 3 characters long." unless user.username and user.username.length >= 3
+
+    throw new Meteor.Error 403, "Username must contain only a-zA-Z0-9_- characters." unless USERNAME_REGEX.test user.username
+
+    throw new Meteor.Error 403, 'Username conflicts with existing slug.' if user.username in FORBIDDEN_USERNAMES
+
+    # TODO: Validate e-mail

     user.person =
       _id: personId


Fixed behaviour of exports global

Fixed behaviour of exports global

--- Sandbox.coffee
+++ Sandbox.coffee
@@ -18,8 +18,11 @@
       value: module

     # Add aliases to module fields
+    exports = module.exports
     Object.defineProperty @, 'exports',
-      get: () -> module.exports
+      get: () -> exports
+      set: (value) ->
+        exports = value
     Object.defineProperty @, 'require',
       value: (id) ->
         module.require.call module, id


FIX: Comment for external name in teaching period modal

--- src/app/admin/modals/teaching-period-settings-modal/teaching-period-settings-modal.coffee
+++ src/app/admin/modals/teaching-period-settings-modal/teaching-period-settings-modal.coffee
@@ -22,7 +22,7 @@
     endOpened: false
   }

-  # Get the confugurable, external name of Doubtfire
+  # Get the external name of Doubtfire
   $scope.externalName = ExternalName

   if $scope.isNew


AcornOptionsView: render must return @

AcornOptionsView: render must return @

--- coffee/src/views/acorn_options_view.coffee
+++ coffee/src/views/acorn_options_view.coffee
@@ -29,3 +29,4 @@
     super
     @$el.empty()
     @$el.html @template @model.attributes
+    @


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/engine.coffee
+++ test/engine.coffee
@@ -182,4 +182,5 @@

 test "#setState", ->

+
 module()


Fix spec file to enable --force-exclusion option in RuboCop

--- spec/linter/rubocop-spec.coffee
+++ spec/linter/rubocop-spec.coffee
@@ -17,10 +17,10 @@
         it 'returns ["/path/to/rubocop", "--format", "json", "/path/to/target.rb"]', ->
           atom.config.set('atom-lint.rubocop.path', '/path/to/rubocop')
           expect(rubocop.buildCommand())
-            .toEqual(['/path/to/rubocop', '--format', 'json', '/path/to/target.rb'])
+            .toEqual(['/path/to/rubocop', '--force-exclusion', '--format', 'json', '/path/to/target.rb'])

       describe 'and config "atom-lint.rubocop.path" is not set', ->
         it 'returns ["rubocop", "--format", "json", "/path/to/target.rb"]', ->
           atom.config.set('atom-lint.rubocop.path', null)
           expect(rubocop.buildCommand())
-            .toEqual(['rubocop', '--format', 'json', '/path/to/target.rb'])
+            .toEqual(['rubocop', '--force-exclusion', '--format', 'json', '/path/to/target.rb'])


Fix spec relying on wrong version of tab text

--- spec/app/tokenized-buffer-spec.coffee
+++ spec/app/tokenized-buffer-spec.coffee
@@ -2,6 +2,7 @@
 LanguageMode = require 'language-mode'
 Buffer = require 'buffer'
 Range = require 'range'
+_ = require 'underscore'

 describe "TokenizedBuffer", ->
   [editSession, tokenizedBuffer, buffer] = []
@@ -162,14 +163,15 @@
         editSession2.destroy()

       it "always renders each tab as its own atomic token with a value of size tabLength", ->
+        tabAsSpaces = _.multiplyString(' ', editSession2.tabLength)
         screenLine0 = tokenizedBuffer.lineForScreenRow(0)
-        expect(screenLine0.text).toBe "# Econ 101#{editSession2.getTabText()}"
+        expect(screenLine0.text).toBe "# Econ 101#{tabAsSpaces}"
         { tokens } = screenLine0
         expect(tokens.length).toBe 3
         expect(tokens[0].value).toBe "#"
         expect(tokens[1].value).toBe " Econ 101"
-        expect(tokens[2].value).toBe editSession2.getTabText()
+        expect(tokens[2].value).toBe tabAsSpaces
         expect(tokens[2].scopes).toEqual tokens[1].scopes
         expect(tokens[2].isAtomic).toBeTruthy()

-        expect(tokenizedBuffer.lineForScreenRow(2).text).toBe "#{editSession2.getTabText()} buy()#{editSession2.getTabText()}while supply > demand"
+        expect(tokenizedBuffer.lineForScreenRow(2).text).toBe "#{tabAsSpaces} buy()#{tabAsSpaces}while supply > demand"


removing dead code

removing dead code

--- src/runtime.coffee
+++ src/runtime.coffee
@@ -135,15 +135,6 @@
       trace "heapifying #{oref} -> #{@string_redirector[key].ref} : '#{cstr.value}'"
     vtrace "redirecting #{oref} -> #{@string_redirector[key].ref}"
     return @string_redirector[key]
-
-  # Used by ZipFile to return unique zip file descriptor IDs.
-  set_zip_descriptor: (zd_obj) ->
-    @zip_descriptors.push zd_obj
-    gLong.fromInt(@zip_descriptors.length - 1)
-  get_zip_descriptor: (zd_long) ->
-    @zip_descriptors[zd_long.toInt()]
-  free_zip_descriptor: (zd_long) ->
-    #delete @zip_descriptors[zd_long.toInt()]

   curr_frame: -> @meta_stack().curr_frame()


Fixed #143 - routes now get a request timestamp.

--- src/middleware/router.coffee
+++ src/middleware/router.coffee
@@ -47,6 +47,7 @@
 				headers: ctx.request.header
 				querystring: ctx.request.querystring
 				method: ctx.request.method
+				timestamp: new Date()
 			routeResponse.response = {}
 			ctx.routes = [] if not ctx.routes
 			ctx.routes.push routeResponse


adding maps 1.  calling create in backbone.js tries to create objects on the server, so I those calls for our client side only ranges 2.  I think the map needs to draw when the div is already on the screen, so I wrap the map calls inside a defer 3.  some of the serialization/deserialization was broken for plot server stuff, so that was fixed 4.  the script_injection of goole maps doesn't really work for plot server, so I just added it to the page dom.

--- static/coffee/common/gmap_plot.coffee
+++ static/coffee/common/gmap_plot.coffee
@@ -59,8 +59,12 @@
     super(_.defaults(options, @default_options))

     # TODO(bryanv): there must be a cleaner way to do this
-    @x_range = Collections('Range1d').create({start: 0, end: 1})
-    @y_range = Collections('Range1d').create({start: 0, end: 1})
+    coll = Collections('Range1d')
+    m = Collections('Range1d').model
+    @x_range = new m({start: 0, end: 1})
+    coll.add(@x_range)
+    @y_range = new m({start: 0, end: 1})
+    coll.add(@y_range)
     @mset('x_range', @x_range, {silent: true})
     @mset('y_range', @y_range, {silent: true})

@@ -100,13 +104,14 @@
       domain_mapper: @xmapper
       codomain_mapper: @ymapper
     })
-
-    pantool = Collections('PanTool').create(
+    coll = Collections('PanTool')
+    m = coll.model
+    pantool = new m(
       dataranges: [@x_range.ref(), @y_range.ref()]
       dimensions: ['width', 'height']
     )
-
-    @mset('tools', [pantool])
+    coll.add(pantool)
+    @mget('tools').push(pantool.ref())

     @requested_padding = {
       top: 0
@@ -230,24 +235,24 @@
     @$el.attr("width", ow).attr('height', oh)
     @gmap_div.attr("style", "top: #{top}px; left: #{left}px; position: absolute")
     @gmap_div.width("#{iw}px").height("#{ih}px")
-
-    mo = @mget('map_options')
-    map_options =
-      center: new google.maps.LatLng(mo.lat, mo.lng)
-      zoom:mo.zoom
-      disableDefaultUI: true
-      mapTypeId: google.maps.MapTypeId.SATELLITE
-
-    # Create the map with above options in div
-    @map = new google.maps.Map(@gmap_div[0], map_options)
-    google.maps.event.addListener(@map, 'bounds_changed', () =>
-      bds = @map.getBounds()
-      ne = bds.getNorthEast()
-      sw = bds.getSouthWest()
-      @x_range.set({start: sw.lng(), end: ne.lng(), silent:true})
-      @y_range.set({start: sw.lat(), end: ne.lat()})
-    )
-
+    build_map = () =>
+      mo = @mget('map_options')
+      map_options =
+        center: new google.maps.LatLng(mo.lat, mo.lng)
+        zoom:mo.zoom
+        disableDefaultUI: true
+        mapTypeId: google.maps.MapTypeId.SATELLITE
+
+      # Create the map with above options in div
+      @map = new google.maps.Map(@gmap_div[0], map_options)
+      google.maps.event.addListener(@map, 'bounds_changed', () =>
+        bds = @map.getBounds()
+        ne = bds.getNorthEast()
+        sw = bds.getSouthWest()
+        @x_range.set({start: sw.lng(), end: ne.lng(), silent:true})
+        @y_range.set({start: sw.lat(), end: ne.lat()})
+      )
+    _.defer(build_map)
     @ctx = @canvas[0].getContext('2d')
     if full_render
       @render()


KD: bind3 => bind (this was a leftover from testing)

--- client/Framework/core/KD.coffee
+++ client/Framework/core/KD.coffee
@@ -1,4 +1,4 @@
-Function::bind3 or= (context) ->
+Function::bind or= (context) ->
   if 1 < arguments.length
     args = [].slice.call arguments, 1
     return => @apply context, if arguments.length then args.concat [].slice.call arguments else args


fix schema: some fields are optional

fix schema: some fields are optional

--- common.coffee
+++ common.coffee
@@ -37,6 +37,7 @@
   description: {
     type: String,
     label: "Beschreibung",
+    optional: true,
     max: 200
   },
   phone: {
@@ -72,6 +73,7 @@
   description: {
     type: String,
     label: "Beschreibung",
+    optional: true,
     max: 200
   }
 })


Follow mixin

Follow mixin

--- test/follow.coffee
+++ test/follow.coffee
@@ -20,6 +20,7 @@

   fastEnemy = GameObject
     x: 50
-
+    y: 100
+    width:

 module()


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/follow.coffee
+++ test/follow.coffee
@@ -16,7 +16,7 @@
   enemy.include(Follow)
   enemy.follow(player)

-  ok enemy.I.velocity.equal(Point(1, 0)), 'enemy should head toward player with velocity Point(1, 0)'
+  ok enemy.I.direction.equal(Point(1, 0)), 'enemy should head toward player with velocity Point(1, 0)'

   fastEnemy = GameObject
     x: 50


Fix NaN error. Now filters NaN's rx from ThingSpeak

--- web/site/src/javascript/DrawerBase.coffee
+++ web/site/src/javascript/DrawerBase.coffee
@@ -62,16 +62,24 @@
     do @comf_dataMgrs[__.id_of sensor].begin

   _bind_env_data_to_sensor: (sensor, nvData) ->
+    nvData = @_filter_nvData(nvData)
     sensor.properties.temperatures = (d.y for d in nvData?[0].values)
     sensor.properties.humidities   = (d.y for d in nvData?[1].values)
     sensor.properties.th_times     = (d.x for d in nvData?[0].values)
     sensor.properties.env_nvData   = nvData

   _bind_comf_data_to_sensor: (sensor, nvData) ->
+    nvData = @_filter_nvData(nvData)
     sensor.properties.comfortabilities =
       (d.y for d in nvData?[0].values)
     sensor.properties.comf_times =
       (d.x for d in nvData?[0].values)
     sensor.properties.comf_nvData = nvData

+  _filter_nvData: (nvData) ->
+    isvalid = (d) -> !isNaN(d?.x) and !isNaN(d?.y)
+    filtered = __.clone nvData
+    dataset.values = dataset.values.filter isvalid for dataset in filtered
+    return filtered
+
 module.exports = DrawerBase


2017-02-19 23:17:48

2017-02-19 23:17:48

--- def/menu_win32.cson
+++ def/menu_win32.cson
@@ -17,6 +17,8 @@
 				submenu:
 					"Clear Project History":
 						value: "清除项目历史"
+			"Reopen Last &Item":
+ 				value: "重新打开上一次项目(&I)"
 			"Open Recent":
 				value: "最近打开的文件..."
 				submenu:


Complete rendering of tags

Complete rendering of tags

--- client/message.coffee
+++ client/message.coffee
@@ -668,15 +668,6 @@
 tabindex = (i) ->
   1 + 20 * Template.instance().count + parseInt(i ? 0)

-absentTags = ->
-  data = Template.currentData()
-  Tags.find
-    group: data.group
-    key: $nin: _.keys data.tags ? {}
-    deleted: false
-  ,
-    sort: ['key']
-
 routeHere = (id) ->
   id? and Router.current().route?.getName() == 'message' and
   Router.current().params?.message == id
@@ -694,7 +685,6 @@
 Template.submessage.helpers
   Submessage: -> Submessage
   message: -> @
-  canReply: -> canPost @group, @_id
   editingRV: -> Template.instance().editing.get()
   editingNR: -> Tracker.nonreactive -> Template.instance().editing.get()
   editTitle: -> Template.instance().editTitle.get()
@@ -960,10 +950,6 @@
     history = messageHistory.get(@_id) ? @
     'image' == fileType history.file

-  absentTags: absentTags
-  absentTagsCount: ->
-    absentTags().count()
-
 Template.messageTags.helpers
   tags: historify 'tags', sortTags

@@ -1889,6 +1875,15 @@
       else
         formatted
   , [historified.file, historified._id]
+  absentTags = useTracker ->
+    Tags.find
+      group: message.group
+      key: $nin: _.keys message.tags ? {}
+      deleted: false
+    ,
+      sort: ['key']
+    .fetch()
+  , [message.group, _.keys(message.tags ? {}).join '\n']
   can = useTracker ->
     delete: canDelete message._id
     undelete: canUndelete message._id
@@ -2054,19 +2049,20 @@

-          {###
-            each tags
-              span.label.label-default.tag.tagWithRemove
-                | #{key}
-                span.tagRemove.fas.fa-times-circle(aria-label="Remove",data-tag=key)
-              |
-          ###}
+            {for tag in sortTags message.tags
+              <>
+
+                  {tag.key + ' '}
+
+
+                {' '}
+              </>
+            }

             <button className="btn btn-default label label-default dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
-
-              | Tag
-
+
+              Tag
             </button>

@@ -2081,13 +2077,16 @@
                   </form>

-              {###
-              if absentTagsCount
-                li.divider(role="separator")
-              each absentTags
-                li
-                  a.tagAdd(href="#",data-tag=key) #{key}
-              ###}
+              {if absentTags.length
+                <>
+

+                  {for tag in absentTags
+

+                      {tag.key}
+

+                  }
+                </>
+              }

           <MessageLabels message={message}/>


number_extensions

--- src/number_extensions.coffee
+++ src/number_extensions.coffee
@@ -391,9 +391,7 @@

 @name d
 @methodOf Number#
-
-@type Number
-@returns The sum of rolling this many sides-sided dice
+@returns {Number} The sum of rolling this many sides-sided dice
 ###
 Number::d = (sides) ->
   sum = 0


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/number_extensions.coffee
+++ src/number_extensions.coffee
@@ -73,7 +73,7 @@
 Number::round = ->
   Math.round(this)

-Number
+Number::circ

 ###*
 Returns a number whose value is limited to the given range.


Fix a bug where a peer's controller is undefined

--- src/public/models/remote.peer.coffee
+++ src/public/models/remote.peer.coffee
@@ -7,7 +7,7 @@

 	class Remote.Peer extends Remote

-		type: 'peer'
+		role: 'peer'

 		# Provides default server configuration for RTCPeerConnection.
 		_serverConfiguration:
@@ -52,7 +52,7 @@
 		#
 		connect: ( ) ->
 			@_isConnector = true
-			@controller.emitTo(@id, 'peer.connectionRequest', @controller.id, @controller.type)
+			@_controller.emitTo(@id, 'peer.connectionRequest', @_controller.id, @_controller.type)

 			channel = @_connection.createDataChannel('a', @_channelConfiguration)
 			@_connection.createOffer(@_onLocalDescription)
@@ -107,7 +107,7 @@
 		#
 		_onLocalDescription: ( description ) =>
 			@_connection.setLocalDescription(description)
-			@controller.emitTo(@id, 'peer.setRemoteDescription', @controller.id, description)
+			@_controller.emitTo(@id, 'peer.setRemoteDescription', @_controller.id, description)

 		# Is called when a remote description has been received. It will create an answer.
 		#
@@ -127,7 +127,7 @@
 		#
 		_onIceCandidate: ( event ) =>
 			if event.candidate?
-				@controller.emitTo(@id, 'peer.addIceCandidate', @controller.id, event.candidate)
+				@_controller.emitTo(@id, 'peer.addIceCandidate', @_controller.id, event.candidate)

 		# Is called when the remote wants to add an ice candidate.
 		#


export AppAwareMixin

export AppAwareMixin

--- app/app.coffee
+++ app/app.coffee
@@ -51,7 +51,7 @@
       _.without v, modal
   , 0

-AppAwareMixin =
+exports.AppAwareMixin = AppAwareMixin =
   contextTypes:
     app: React.PropTypes.object


added some tracking for copy to clipboard

--- client/singlePage/helper.coffee
+++ client/singlePage/helper.coffee
@@ -144,9 +144,15 @@
       return null
     ranking = $(event.target).parents('.stars').data('stars')
     entryId = FlowRouter.getParam('id')
-
     Meteor.call('vote', ranking, entryId)

+  # Track bps copied to clipbard
+  'click #bluePrintBtn': ()->
+    GAnalytics.event("bluePrintBtn","copy_to_clip_board");
+
+  #track bp strings that are viewed
+  'click #viewString': ()->
+    GAnalytics.event("bluePrintBtn","view_blueprint_string");

 Template.single.onRendered ()=>
   if hasFlash


Create .build directory in builder.

Create .build directory in builder.

--- Builder.coffee
+++ Builder.coffee
@@ -22,6 +22,8 @@
   buildClient: (options)->
     @config = require('koding-config-manager').load("main.#{options.configFile}")
     @incluesFileTime = 0
+
+    fs.mkdirSync ".build"
     @compileChanged options, true

   compileChanged: (options, initial)->


'Updating for version 0.12.5dev10'

'Updating for version 0.12.5dev10'

--- bokehjs/src/coffee/version.coffee
+++ bokehjs/src/coffee/version.coffee
@@ -1 +1 @@
-export version = '0.12.5dev9'
+export version = '0.12.5dev10'


clean up

clean up

--- src/jquery.transparency.coffee
+++ src/jquery.transparency.coffee
@@ -25,7 +25,12 @@

       fragment = context.transparency.fragment
       # Attach the template instance elements to DocumentFragment for rendering
-      (fragment.appendChild n) for n in context.transparency.instances[i]
+      # Also, associate model object with instance element
+      for n in context.transparency.instances[i]
+        fragment.appendChild n
+        if isArray
+          n.transparency ||= {}
+          n.transparency.model = object

       renderValues      fragment, object
       renderDirectives  fragment, object, directives
@@ -33,8 +38,6 @@

       # Attach the template instance elements back to the context
       context.appendChild fragment
-
-      context.lastChild.transparency = { model : object } if isArray

     # Finally, put the context element back to it's original place in DOM
     if sibling then parent?.insertBefore(context, sibling) else parent?.appendChild context


Comments for Vex/underscore configurations in initialize

--- SingularityUI/app/initialize.coffee
+++ SingularityUI/app/initialize.coffee
@@ -3,7 +3,10 @@
 window.constants = require 'constants'
 window.app = require 'application'

+# Make all string methods available on _
 _.mixin _.string.exports()
+
+# Set Vex default className
 vex.defaultOptions.className = 'vex-theme-default'

 $ -> app.initialize()


Flag to turn off automatic refreshing during development.

--- SingularityUI/app/lib/router.coffee
+++ SingularityUI/app/lib/router.coffee
@@ -50,7 +50,7 @@
     , 20 * 1000

 refresh = ->
-    if not $('body > .vex').length and not windowBlurred
+    if not $('body > .vex').length and not windowBlurred and env.disablePageRefresh isnt true
         app.views.current?.refresh?()

 class Router extends Backbone.Router
@@ -214,4 +214,4 @@
             app.views.current = app.views.pageNotFound
             app.show app.views.pageNotFound

-module.exports = Router+module.exports = Router


Stop tailing if scroll to top is clicked

--- SingularityUI/app/views/tail.coffee
+++ SingularityUI/app/views/tail.coffee
@@ -215,10 +215,13 @@

     goToTop: =>
         if @collection.getMinOffset() is 0
+            @stopTailing() if @isTailing
             @scrollToTop()
         else
             @collection.reset()
-            @collection.fetchFromStart().done @scrollToTop
+            @collection.fetchFromStart().done =>
+              @stopTailing() if @isTailing
+              @scrollToTop()

     goToBottom: =>
         if @collection.state.get('moreToFetch') is true


different order generation strategies for market perf

--- perf/market_throughput.coffee
+++ perf/market_throughput.coffee
@@ -1,3 +1,4 @@
+hd = require('heapdump')
 Market = require('../lib/datastore/market')
 Order = require ('../lib/datastore/order')
 DQ = require('deque')
@@ -12,16 +13,29 @@
 makeRandomOrder = ->
   currencies = ['BTC', 'USD']
   currencies = currencies.reverse() if randomBool()
-  new Order({name: "user-#{acctID++}"}, currencies[0], randomInt(1, 5), currencies[1], randomInt(1, 5))
+  new Order({name: "user-#{acctID++}"}, currencies[0], randomInt(1, 5000), currencies[1], randomInt(1, 5000))
+
+makeMatchingOrder = (n) ->
+  currencies = if (n % 5) then ['BTC', 'USD'] else ['USD', 'BTC']
+  amt = if (n % 5) then 0.25 else 1
+  new Order({name: "user-#{acctID++}"}, currencies[0], amt, currencies[1], amt)
+
+mixAndMatch = (n) ->
+  if (n % 10) >= 5
+    makeRandomOrder()
+  else
+    makeMatchingOrder(n)

 market = new Market('BTC', 'USD')

-count = 1000000
+count = 100000000000
 block_size = 10000
 iterations = Math.floor(count/block_size)

 console.log "Generating #{block_size} random orders..."
 orders = (makeRandomOrder() for _ in [1..block_size])
+#orders = (makeMatchingOrder(n) for n in [1..block_size])
+#orders = (mixAndMatch(n) for n in [1..block_size])

 rl = require('readline').createInterface(process.stdin, process.stdout)
 rl.setPrompt('', 0)


Stored introId instances to introInstances object.

--- core/KD.coffee
+++ core/KD.coffee
@@ -120,7 +120,9 @@
   registerInstance : (anInstance)->
     warn "Instance being overwritten!!", anInstance  if @instances[anInstance.id]
     @instances[anInstance.id] = anInstance
-    @introInstances[anInstance.id] = anInstance if anInstance.getOptions().introId
+
+    {introId} = anInstance.getOptions()
+    @introInstances[introId] = anInstance if introId
     # @classes[anInstance.constructor.name] ?= anInstance.constructor

   unregisterInstance: (anInstanceId)->


log stats improvement

log stats improvement

--- server/api/lib/log.coffee
+++ server/api/lib/log.coffee
@@ -51,7 +51,7 @@
     authRequired: if API.settings.dev then undefined else 'root'
     action: () ->
       if API.settings.log?.bulk isnt 0 and API.settings.log?.bulk isnt false
-        return {length: _log_stack.length, last: moment(_log_last, "x").format("YYYY-MM-DD HHmm.ss")}
+        return {current: _lsp, a: _ls.a.length, b: _ls.b.length, last: moment(_log_last, "x").format("YYYY-MM-DD HHmm.ss")}
       else
         return {info: 'Log stack is not in use'}


Change debounce to a throttle so it doesn't halt

Otherwise lots of continuous updates will make it wait indefinitely.

--- src/angularD3/directives/chart.coffee
+++ src/angularD3/directives/chart.coffee
@@ -21,7 +21,6 @@
       .attr("transform", "translate(" + @margin.left + "," + @margin.top + ")")

     @getChart = () -> chart
-
     @addScale = (name, scale) -> scales[name] = scale
     @getScale = (name) -> scales[name].scale
     @registerElement = (el) -> elements.push el
@@ -29,13 +28,14 @@
     debounce = null
     @redraw = ->
       data = $scope.$eval(binding)
-      $timeout.cancel(debounce) if debounce
+      return if debounce
       debounce = $timeout =>
+        debounce = null
         for name, scale of scales
           scale.redraw(data)
         for element in elements
           element.redraw(data)
-      , 200
+      , $attrs.updateInterval or 200
     $window.addEventListener 'resize', @redraw
     $scope.$watch binding, @redraw, true


moved mongoose schemas out of model file into service

--- coffee/service.coffee
+++ coffee/service.coffee
@@ -1,11 +1,16 @@
 mongoose = require 'mongoose'

-schema = require './model'
+GeoSchema = new mongoose.Schema
+  type: String
+  lnglats: []
+  created: { type: Date, default: Date.now }
+  meta: {}

-module.exports = (url) ->
-  db = mongoose.connect url
-  Geo = db.model 'Geo', schema.Geo
+module.exports = (opt) ->
+  db = if opt.db? then opt.db else mongoose.connect opt.url
+  Geo = db.model (opt.collectionName or 'Geo'), GeoSchema

+  db: db
   createGeo: (geo, callback) ->
     new Geo(geoToDoc geo).save callback

@@ -22,16 +27,19 @@
     Geo.findById id, (err, doc) -> callback err, docToGeo doc

   getGeos: (callback, opt) ->
-    {find, sort} = opt if opt?
+    {find, sort, limit} = opt if opt?
     q = Geo.find find
     q = q.sort (if sort? then sort else 'created')
+    if limit? then q = q.limit limit
     q.exec (err, docs) -> callback err, (docToGeo doc for doc in docs)

 exports.docToGeo = docToGeo = (doc) ->
-  lnglat: doc.lnglat
+  type: doc.type
+  lnglats: doc.lnglats
   meta: doc.meta
   created: doc.created

 exports.geoToDoc = geoToDoc = (geo) ->
-  lnglat: geo.lnglat
+  type: geo.type
+  lnglats: geo.lnglats
   meta: geo.meta


Fixes #314. Beauify On Save will save then beautify the file on disk

--- src/beautify.coffee
+++ src/beautify.coffee
@@ -343,7 +343,24 @@
 handleSaveEvent = =>
   atom.workspace.observeTextEditors (editor) =>
     buffer = editor.getBuffer()
-    disposable = buffer.onWillSave(beautify.bind(@, {onSave:true}))
+    disposable = buffer.onDidSave(({path:filePath}) =>
+        path ?= require('path')
+        # Get Grammar
+        grammar = editor.getGrammar().name
+        # Get language
+        fileExtension = path.extname(filePath)
+        languages = beautifier.languages.getLanguages({grammar, fileExtension})
+        if languages.length < 1
+            return
+        # TODO: select appropriate language
+        language = languages[0]
+        # Get language config
+        beautifyOnSave = atom.config.get("atom-beautify.language_#{language.namespace}_beautify_on_save")
+        if beautifyOnSave
+            beautifyFilePath(filePath, ->
+                buffer.reload()
+            )
+    )
     plugin.subscribe disposable

 {Subscriber} = require path.join(atom.packages.resourcePath, 'node_modules', 'emissary')


Removed flashing title icons

Removed flashing title icons

--- client/coffeescripts/chat.coffee
+++ client/coffeescripts/chat.coffee
@@ -65,9 +65,9 @@
 #bool representing if the user is at the bottom of chatbox
 document.bottomCheck = null

-#the favicons for flashing titles
-greenFavicon = "/favicon.ico"
-blueFavicon = "/favicon2.ico"
+#the favicons for flashing titles NEEDS TO BE UPDATED
+greenFavicon = null
+blueFavicon = null

 #indicates if a message update was forced
 forcedUpdate = false